-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide ForwardingMediaSource to simplify custom implementations #7279
Comments
Proof of concept implementation looks like: public class CustomStartPositionSource extends CompositeMediaSource<Void> {
private final MediaSource mWrappedSource;
private final long mStartPos;
private Timeline mTimeline;
public CustomStartPositionSource(MediaSource wrappedSource, long startPositionMs) {
this.mWrappedSource = wrappedSource;
this.mStartPos = C.msToUs(startPositionMs);
}
// MediaSource
@Nullable
@Override
public Object getTag() {
return mWrappedSource.getTag();
}
// CompositeMediaSource
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(mediaTransferListener);
prepareChildSource(/* id= */ null, mWrappedSource);
}
@NonNull
@Override
public MediaPeriod createPeriod(
@NonNull MediaPeriodId id,
@NonNull Allocator allocator,
long startPositionUs) {
return mWrappedSource.createPeriod(id, allocator, startPositionUs);
}
@Override
public void releasePeriod(@NonNull MediaPeriod mediaPeriod) {
mWrappedSource.releasePeriod(mediaPeriod);
}
@Override
protected void releaseSourceInternal() {
super.releaseSourceInternal();
mTimeline = null;
}
@Override
protected void onChildSourceInfoRefreshed(
@NonNull Void id,
@NonNull MediaSource mediaSource,
@NonNull Timeline timeline) {
mTimeline = new CustomStartPositionTimeline(timeline, mStartPos);
refreshSourceInfo(mTimeline);
}
private static final class CustomStartPositionTimeline extends ForwardingTimeline {
private final long defaultPositionUs;
CustomStartPositionTimeline(Timeline wrapped, long defaultPositionUs) {
super(wrapped);
this.defaultPositionUs = defaultPositionUs;
}
@NonNull
@Override
public Window getWindow(
int windowIndex,
@NonNull Window window,
long defaultPositionProjectionUs) {
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
window.defaultPositionUs = defaultPositionUs;
return window;
}
}
} |
Another one, purely on public class CustomStartPositionSource extends BaseMediaSource {
private final MediaSource mWrappedSource;
private final long mStartPosUs;
private Timeline mTimeline;
private final MediaSource.MediaSourceCaller mCaller;
CustomStartPositionSource(@NonNull MediaSource wrappedSource, long startPositionMs) {
this.mWrappedSource = wrappedSource;
this.mStartPosUs = C.msToUs(startPositionMs);
mCaller = (source, timeline) -> {
mTimeline = new CustomStartPositionTimeline(timeline, mStartPosUs);
refreshSourceInfo(mTimeline);
};
}
// BaseMediaSource
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
mWrappedSource.prepareSource(mCaller, mediaTransferListener);
if (!isEnabled()) {
mWrappedSource.disable(mCaller);
}
}
@Override
protected void enableInternal() {
mWrappedSource.enable(mCaller);
}
@Override
protected void disableInternal() {
mWrappedSource.disable(mCaller);
}
@Override
protected void releaseSourceInternal() {
mWrappedSource.releaseSource(mCaller);
mTimeline = null;
}
// MediaSource
@Nullable
public Timeline getInitialTimeline() {
return mWrappedSource.getInitialTimeline();
}
@Override
public boolean isSingleWindow() {
return mWrappedSource.isSingleWindow();
}
@Nullable
@Override
public Object getTag() {
return mWrappedSource.getTag();
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
mWrappedSource.maybeThrowSourceInfoRefreshError();
}
@NonNull
@Override
public MediaPeriod createPeriod(
@NonNull MediaPeriodId id,
@NonNull Allocator allocator,
long startPositionUs) {
return mWrappedSource.createPeriod(id, allocator, startPositionUs);
}
@Override
public void releasePeriod(@NonNull MediaPeriod mediaPeriod) {
mWrappedSource.releasePeriod(mediaPeriod);
}
private static final class CustomStartPositionTimeline extends ForwardingTimeline {
private final long defaultPositionUs;
CustomStartPositionTimeline(@NonNull Timeline wrapped, long defaultPositionUs) {
super(wrapped);
this.defaultPositionUs = defaultPositionUs;
}
@NonNull
@Override
public Window getWindow(
int windowIndex,
@NonNull Window window,
long defaultPositionProjectionUs) {
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
window.defaultPositionUs = defaultPositionUs;
return window;
}
}
} |
Can you clarify why you would extend |
Honestly speaking diving into whole sources/timelines/periods/windows is pretty hard stuff, when you have no experience with it. And since source code in the example is a bit out of date - I was looking for existing solutions, which wraps another MediaSource. So there were |
ok, here is public class CustomStartPositionSource implements MediaSource {
private final MediaSource mWrappedSource;
private final long mStartPosUs;
public CustomStartPositionSource(@NonNull MediaSource wrappedSource, long startPositionMs) {
this.mWrappedSource = wrappedSource;
this.mStartPosUs = C.msToUs(startPositionMs);
}
// MediaSource
@Override
public void addEventListener(@NonNull Handler handler,
@NonNull MediaSourceEventListener eventListener) {
mWrappedSource.addEventListener(handler, eventListener);
}
@Override
public void removeEventListener(@NonNull MediaSourceEventListener eventListener) {
mWrappedSource.removeEventListener(eventListener);
}
@Override
public void addDrmEventListener(@NonNull Handler handler,
@NonNull DrmSessionEventListener eventListener) {
mWrappedSource.addDrmEventListener(handler, eventListener);
}
@Override
public void removeDrmEventListener(@NonNull DrmSessionEventListener eventListener) {
mWrappedSource.removeDrmEventListener(eventListener);
}
@Override
@Nullable
public Timeline getInitialTimeline() {
return mWrappedSource.getInitialTimeline();
}
@Override
public boolean isSingleWindow() {
return mWrappedSource.isSingleWindow();
}
@Nullable
@Override
public Object getTag() {
return mWrappedSource.getTag();
}
@Override
public void prepareSource(
@NonNull MediaSourceCaller caller,
@Nullable TransferListener mediaTransferListener) {
mWrappedSource.prepareSource(
(source, timeline) ->
caller.onSourceInfoRefreshed(source, new CustomStartPositionTimeline(timeline, mStartPosUs)),
mediaTransferListener
);
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
mWrappedSource.maybeThrowSourceInfoRefreshError();
}
@Override
public void enable(@NonNull MediaSourceCaller caller) {
mWrappedSource.enable(caller);
}
@NonNull
@Override
public MediaPeriod createPeriod(
@NonNull MediaPeriodId id,
@NonNull Allocator allocator,
long startPositionUs) {
return mWrappedSource.createPeriod(id, allocator, startPositionUs);
}
@Override
public void releasePeriod(@NonNull MediaPeriod mediaPeriod) {
mWrappedSource.releasePeriod(mediaPeriod);
}
@Override
public void disable(@NonNull MediaSourceCaller caller) {
mWrappedSource.disable(caller);
}
@Override
public void releaseSource(@NonNull MediaSourceCaller caller) {
mWrappedSource.releaseSource(caller);
}
private static final class CustomStartPositionTimeline extends ForwardingTimeline {
private final long defaultPositionUs;
CustomStartPositionTimeline(@NonNull Timeline wrapped, long defaultPositionUs) {
super(wrapped);
this.defaultPositionUs = defaultPositionUs;
}
@NonNull
@Override
public Window getWindow(
int windowIndex,
@NonNull Window window,
long defaultPositionProjectionUs) {
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
window.defaultPositionUs = defaultPositionUs;
return window;
}
}
} So it solves my problem, but I think that custom "default playback pos" strategy for HLS can be pretty useful. |
|
When wrapping one or more other media sources you should use
Sounds like a helpful addition for cases where |
@tonihei, there is message forwarding in |
Should probably work. It's still easier to rely on an existing wrapper for boilerplate code mostly because code changes in the future will automatically update the base classes, whereas if you implement the interface directly, you need to update your implementation manually. |
@tonihei. You were right. :) |
Inspiration taken from: - google/ExoPlayer#7087 - google/ExoPlayer#5883 - google/ExoPlayer#7279
This issue has been addressed by androidx/media#123. |
[REQUIRED] Use case description
Current implementation of
HlsMediaSource
starts with default position:So, if HLS has lots of segments or they are long enough - default start position will be pretty far from it's 0. This is desired behavior when playing "live" media.
But, for example, "catchup" of IPTV services is implemented in the way, when url identifies specific time (wall clock), and when player starts playback - it should start from 0 instead of "live edge" to match target wall clock.
Alternatives considered
I have tried:
MediaItem.ClippingProperties
- does not work (since it clips only)Proposed solution
I have tried:
Window.defaultPositionUs
(based on Allow the app to override the start position when playback transitions to another source #2403 (comment)) - worksI think that some functionality can be provided by library itself:
CustomStartPositionSource
CompositeMediaSource
should be used (the same as it's used byClippingMediaSource
), which looks a bit of overkill.The text was updated successfully, but these errors were encountered: