3

Has anyone faced issues with TextureView and MediaPlayer on Android 4.2.2?

SOLVED: See answer below and how to serve files from an embedded HTTP server.

UPDATE: The videos which fail to display if embedded in the res/raw folder play just fine if they are hosted online. See below for example:

// This works
mMediaPlayer.setDataSource("http://someserver.com/test.mp4");
//This does not work. The file is the same as the one above but placed in res/raw.
String vidpath = "android.resource://" + getActivity().getPackageName() + "/" + R.raw.test;
mMediaPlayer.setDataSource(getActivity(), Uri.parse(vidpath));

We need a texture view so we can apply some effects to it. The TextureView is used to display a video from the MediaPlayer. The application works on Android 4.1, 4.3 and 4.4 (on many devices including the old Nexus S up to the Nexus 10 and Note 3) but on 4.2.2 the TextureView becomes black. There are no errors or exceptions reported by the MediaPlayer and the reported video sizes are correct. Testing a SurfaceView on this particular device displays the video, but then we can't manipulate the view the way we need to.

One interesting bit is that a TextureView works on that device if playing the Nasa Live Streaming Video feed and some other streaming m3u8 files (http://www.nasa.gov/multimedia/nasatv/NTV-Public-IPS.m3u8) but we need to play embedded videos from the raw folder. We noticed however that at the very top of the TextureView, there's a 4x1 pixel line which keeps blinking some colors very rapidly. I wonder if the media player is rendering the video on that hairline or maybe it is an encoding problem or a hardware issue (this particular 4.2.2 device is a mimic of the iPad mini called... the haiPad >.< (which of course is the client's target device - hate you Murphy)).

Here's the info I could gather about the video which is failing to play:

MPEG-4 (Base Media / Version 2): 375 KiB, 5s 568ms
1 Video stream: AVC
1 Audio stream: AAC
Overall bit rate mode: Variable
Overall bit rate: 551 Kbps
Encoded date: UTC 2010-03-20 21:29:11
Tagged date: UTC 2010-03-20 21:29:12
Writing application: HandBrake 0.9.4 2009112300
Video: 466 Kbps, 560*320 (16:9), at 30.000 fps, AVC (Baseline@L3.0) (2 ref Frames)

Anyone has any pointers?

AngraX
  • 1,803
  • 2
  • 18
  • 30

1 Answers1

3

For anyone facing similar issues, this is what worked for us.

We are still not sure why playing embedded videos with the app does not work. We tried res/raw, assets and copying to the internal storage and sd card.

Since it was able to play videos from an HTTP server, we ended up embedding a light weight HTTP server within the app to serve the files directly from the assets directory. Here I'm using nanohttpd as an embedded server.

For it to work, I just need to place the video in the assets folder. For example assets/animation/animation1.mp4. When referencing the file, you pass "animation/animation1.mp4" as a path and the server will serve the file from the assets directory.

Application class starting the http server and registering it as a service.

public class MyApplication extends Application {

    private NanoServer mNanoServer;

    @Override
    public void onCreate() {

        super.onCreate();
        mNanoServer = new NanoServer(0, getApplicationContext());

        try {

            mNanoServer.start();
        } catch (IOException e) {

            Log.e("Unable to start embeded video file server. Animations will be disabled.",
                    "MyApplication", e);
        }
    }

    @Override
    public void onTerminate() {

        mNanoServer.stop();
        super.onTerminate();
    }

    @Override
    public Object getSystemService(String name) {

        if (name.equals(NanoServer.SYSTEM_SERVICE_NAME)) {
            // TODO Maybe we should check if the server is alive and create a new one if it is not.
            // How often can the server crash?
            return mNanoServer;
        }

        return super.getSystemService(name);
    }
}

The NanoServer class

/*
 * TODO Document this.
 */
public class NanoServer extends NanoHTTPD {

    public static final String SYSTEM_SERVICE_NAME = "NANO_EMBEDDED_SYSTEM_HTTP_SERVER";
    private Context mContext;

    public NanoServer(int port, Context context) {
        super(port);
        this.mContext = context;
    }

    public NanoServer(String hostname, int port) {
        super(hostname, port);
    }

    @Override
    public Response serve(IHTTPSession session) {

        try {

            Uri uri = Uri.parse(session.getUri());
            String fileToServe = normalizePath(uri.getPath());

            return new Response(Status.OK, "video/mp4", (InputStream) mContext.getAssets().open(fileToServe));
        } catch (IOException e) {

            return new Response(Status.INTERNAL_ERROR, "", "");
        }
    }

    private String normalizePath(String path) {

        return path.replaceAll("^/+", "");
    }

    public String getUrlFor(String filePath) {

        return String.format(Locale.ENGLISH, "http://localhost:%d/%s", getListeningPort(), filePath);
    }
}

Using it in a Fragment

if (fileToPlay != null) {

    mTextureView = (TextureView) inflatedView.findViewById(R.id.backgroundAnimation);
    mTextureView.setSurfaceTextureListener(new VideoTextureListener());
}

...

private final class VideoTextureListener implements SurfaceTextureListener {
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {

        mMediaPlayer.release();
        mMediaPlayer = null;
        return true;
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

        Surface s = new Surface(surface);

        try {

            NanoServer server =
                    (NanoServer) getActivity().getApplication().getSystemService(NanoServer.SYSTEM_SERVICE_NAME);
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setSurface(s);
            mMediaPlayer.setDataSource(server.getUrlFor(mHotspotTemplate.animation));
            mMediaPlayer.prepareAsync();
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setVolume(0, 0);

            mMediaPlayer.setOnErrorListener(new OnErrorListener() {

                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {

                    mMediaPlayer.release();
                    mTextureView.setVisibility(View.INVISIBLE);
                    return true;
                }
            });
            mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {

                @Override
                public void onPrepared(MediaPlayer mp) {

                    mMediaPlayer.start();
                }
            });

            mMediaPlayer.setLooping(true);
            mMediaPlayer.setVolume(0, 0);
        } catch (Throwable e) {

            if (mMediaPlayer != null) {

                mMediaPlayer.release();
            }

            mTextureView.setVisibility(View.INVISIBLE);
        }
    }
}
AngraX
  • 1,803
  • 2
  • 18
  • 30