Skip to content
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

Playing from memory (byte[]) #7228

Closed
danergo opened this issue Apr 13, 2020 · 9 comments
Closed

Playing from memory (byte[]) #7228

danergo opened this issue Apr 13, 2020 · 9 comments
Assignees
Labels

Comments

@danergo
Copy link

danergo commented Apr 13, 2020

[REQUIRED] Searched documentation and issues

[REQUIRED] Question

I have a fragmented mp4 with h264 video which is coming as byte[] from a server.
It is a live stream, i.e. it is not possible to load the whole stream at once.
So it is coming from the server chunk-by-chunk (HTML5 video player via MSE can play it nice and smooth).

I've created a custom DataSource:

public class MediaDispatcher implements DataSource {
    public static final String LOG_TAG = "MediaDispatcher";
    private DataSpec mDataSpec;
    private String mBuffer;

    @Override
    public void addTransferListener(TransferListener transferListener) {
        Log.d(LOG_TAG, "addTransferListener");
    }

    @Override
    public long open(DataSpec dataSpec) throws IOException {
        Log.d(LOG_TAG, "open");

        mDataSpec = dataSpec;
        return Long.MAX_VALUE;
    }

    @Override
    public int read(byte[] buffer, int offset, int readLength) {
        Log.d(LOG_TAG, "read, readLength=" + readLength);

        if (0 == readLength) return 0;

        if (readLength > mBuffer.length()) buffer = mBuffer.getBytes();

        return mBuffer.length();
    }

    public void write(String buffer) throws InterruptedException {
        //My service calls this
        mBuffer = buffer;
    }

    @Nullable
    @Override
    public Uri getUri() {
        Log.d(LOG_TAG, "getUri");
        return Uri.EMPTY;
    }

    @Override
    public void close() throws IOException {
        Log.d(LOG_TAG, "close");
    }
}

These are my initialization steps:

mplayer = new SimpleExoPlayer.Builder(context).build();
videoLayout.setPlayer(mplayer);

DataSource.Factory factory = new DataSource.Factory() {
    @Override
    public DataSource createDataSource() {
        return new MediaDispatcher();
    }
};
MediaSource mediaSource = new ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.EMPTY);

mplayer.prepare(mediaSource);

However ExoPlayer seems to try reading only 1 byte at a time starting from the 2nd.

How can I ask ExoPlayer to read more data? I.e. read 10K or 20K bytes at once.
(My read function will take care if there is nothing to read: will block.)

A full bug report captured from the device

This is the relevant logcat:
getUri
addTransferListener
open
getUri
getUri
read, readLength=4, offset=0
read, readLength=1, offset=4
read, readLength=1, offset=5
read, readLength=1, offset=6
read, readLength=1, offset=7
... (same line repeats)
close

Link to test content

Test content is not available yet, because a serving method is also needed for testing.
I hope reading of ExoPlayer can be also configured at least with the buffer size.

@danergo
Copy link
Author

danergo commented Apr 13, 2020

Okay, I have some issues in my read callback now seems to move on.

ExoPlayer starts buffering but it does not get through it:

Log:

D/PlayerFragment: onPlayerStateChanged false 2
D/PlayerFragment: onPlayerStateChanged true 2
I/OMXClient: IOmx service obtained
I/OMXMaster: makeComponentInstance(OMX.google.h264.decoder) in [email protected] process
D/EGL_emulation: eglCreateContext: 0xe725f180: maj 3 min 0 rcv 3
D/EGL_emulation: eglMakeCurrent: 0xe725f180: ver 3 0 (tinfo 0xcfe37f10)
E/EGL_emulation: eglQueryContext 32c0  EGL_BAD_ATTRIBUTE
E/EGL_emulation: tid 20520: eglQueryContext(1861): error 0x3004 (EGL_BAD_ATTRIBUTE)
D/SurfaceUtils: connecting to surface 0xce5b5008, reason connectToSurface
I/MediaCodec: [OMX.google.h264.decoder] setting surface generation to 19993602
D/SurfaceUtils: disconnecting from surface 0xce5b5008, reason connectToSurface(reconnect)
D/SurfaceUtils: connecting to surface 0xce5b5008, reason connectToSurface(reconnect)
E/OMXNodeInstance: getExtensionIndex(0xe71a7020:google.h264.decoder, OMX.google.android.index.enableAndroidNativeBuffers) ERROR: UnsupportedIndex(0x8000101a)
E/ACodec: [OMX.google.h264.decoder] setPortMode on output to DynamicANWBuffer failed w/ err -1010
W/OMXNodeInstance: [0xe71a7020:google.h264.decoder] component does not support metadata mode; using fallback
D/SoftVideoDecoderOMXComponent: Color Aspects preference: 1 
E/OMXNodeInstance: setConfig(0xe71a7020:google.h264.decoder, ConfigPriority(0x6f800002)) ERROR: UnsupportedIndex(0x8000101a)
I/ACodec: codec does not support config priority (err -1010)
E/OMXNodeInstance: getConfig(0xe71a7020:google.h264.decoder, ConfigAndroidVendorExtension(0x6f100004)) ERROR: UnsupportedIndex(0x8000101a)
E/OMXNodeInstance: getConfig(0xe71a7020:google.h264.decoder, ??(0x7f000003)) ERROR: UnsupportedSetting(0x80001019)
D/SoftVideoDecoderOMXComponent: Color Aspects preference: 1 
E/OMXNodeInstance: getConfig(0xe71a7020:google.h264.decoder, ??(0x7f000003)) ERROR: UnsupportedSetting(0x80001019)
D/MediaCodec: [OMX.google.h264.decoder] setting dataspace on output surface to #104
.
.
.
D/PlayerFragment: onPlayerStateChanged true 3

I believe 3 means buffering, but the player unfortunately does not get into ready state, and there are bunch of warnings before that.
What am I missing here?

@icbaker icbaker self-assigned this Apr 14, 2020
@icbaker
Copy link
Collaborator

icbaker commented Apr 14, 2020

Your implementation of open() seems incorrect. The documentation says:

Returns:
The number of bytes that can be read from the opened source. For unbounded requests (i.e. requests where DataSpec.length equals C.LENGTH_UNSET) this value is the resolved length of the request, or C.LENGTH_UNSET if the length is still unresolved. For all other requests, the value returned will be equal to the request's DataSpec.length.

Note that C.LENGTH_UNSET == -1 whereas you always return Long.MAX_VALUE without even looking at DataSpec.length.

You also ignore DataSpec.absoluteStreamPosition which tells the DataSource to open the file at an offset.

Your implementation of read() also seems incorrect - you seem to ignore the offset parameter completely.

It is a live stream, i.e. it is not possible to load the whole stream at once.

If you're not able to seek to arbitrary offsets within the 'file' then simply implementing a DataSource and passing it to ProgressiveMediaSource won't work (see Olly's explanation of why random access is needed: #1086 (comment)).

If your goal is to live stream content from a server, have you considered using one of the protocols designed for this like HLS or DASH?

@danergo
Copy link
Author

danergo commented Apr 14, 2020

User error, but some conclusion:

  • ExoPlayer is built to read only one byte at the beginning, there is nothing wrong with it.

  • Number 3 as PlayerState means STATE_READY.

(My PlayerView was hidden, silly mistake.)

This issue shall be closed as it does not contain any more unanswered questions.

@icbaker
Copy link
Collaborator

icbaker commented Apr 14, 2020

User error, but some conclusion:

  • ExoPlayer is built to read only one byte at the beginning, there is nothing wrong with it.

You may be able to mitigate this by wrapping your DataSource in a CacheDataSource. ExoPlayer does indeed try and read little and often - if you want a more 'chunked' behaviour you need to implement it within your DataSource.

  • Number 3 as PlayerState means STATE_READY.

(My PlayerView was hidden, silly mistake.)

This issue shall be closed as it does not contain any more unanswered questions.

@icbaker
Copy link
Collaborator

icbaker commented Apr 14, 2020

And we should be able to play a progressive live stream without using HLS or DASH, but we determine it's live by detecting C.LENGTH_UNSET returned from DataSource.open - so in the case of your code above that wasn't being detected (because of returning Long.MAX_VALUE).

@danergo
Copy link
Author

danergo commented Apr 14, 2020

Thanks for the CacheDataSource, I can live with the current behavior, it's fine.
(Just at the beginning I believed I missed something like not setting the buffer's size.)

This one however I don't get:
"determine it's live by detecting C.LENGTH_UNSET returned from DataSource.read"

From DataSource.read I shall return by the amount of read, no?
And C.LENGTH_UNSET shall be returned from open actually, no?

@icbaker
Copy link
Collaborator

icbaker commented Apr 14, 2020

This one however I don't get:
"determine it's live by detecting C.LENGTH_UNSET returned from DataSource.read"

From DataSource.read I shall return by the amount of read, no?
And C.LENGTH_UNSET shall be returned from open actually, no?

Yes, you're completely right, sorry about that. I'll edit my comment above to avoid tripping up a future reader.

@mobileappspuzzles
Copy link

@danergo I'm also facing same issue, if you solve the issue can explain how can we do stream.

@danergo
Copy link
Author

danergo commented Apr 15, 2020

It's actually not difficult.
All you have to do is to extend from BaseMediaSource and implement at least getUri, open, close and read.

From your open function you shall return with C.LENGTH_UNSET, this way ExoPlayer can

determine it's live. (icbaker)

Then in read you can pass your raw stream data to the buffer and ExoPlayer will play it if your device contains the right decoder and the stream is valid.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants