15

I'm building an app that records and plays back video. I would like to do so without affecting background music playback, i.e. if I begin playing a video, I do not want to pause other apps' audio. However, on Lollipop, Android's VideoView class automatically requests audio focus when the private method VideoView.openVideo() is called:

AudioManager am = (AudioManager) super.getSystemService(name);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

Any suggestions on how to get around this?

diana
  • 151
  • 1
  • 5

5 Answers5

6

Starting with Android SDK 26 you may want to use VideoView and

if(Build.VERSION.SDK_INT >= Build.Version_CODES.O){
     //set this BEFORE start playback
     videoView.setAudioFocusRequest(AudioManager.AUDIOFOCUS_NONE)
}

For older version, there's a workaround described here: https://stackoverflow.com/a/31569930/993439

Basically, copy source code of VideoView and uncomment following lines

AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
longi
  • 11,104
  • 10
  • 55
  • 89
2

I got around this with a stupid solution by copying whole source code of android.widget.VideoView of Lollipop and removing line you mentioned.

Make your own VideoView class. don't use extends VideoView since you can't override openVideo() method.

I don't recommend this as I thinking it's a temporary solution. VideoView Changed a lot between 4.1-5.0 so this can make RuntimeException on Android version other than Lollipop

Edit

I made approach MediaPlayer + SurfaceView as pinxue told us; It respects aspect ratio within viewWidth and viewHeight.

            final String finalFilePath = filePath;

            final SurfaceHolder surfaceHolder = sv.getHolder();
            final MediaPlayer mediaPlayer = new MediaPlayer();
            final LinearLayout.LayoutParams svLayoutParams = new LinearLayout.LayoutParams(viewWidth,viewHeight);
            surfaceHolder.addCallback(new SurfaceHolder.Callback(){

                @Override
                public void surfaceCreated(SurfaceHolder holder) {

                    try {
                        if(isDebug) {
                        System.out.println("setting VideoPath to VideoView: "+finalFilePath);
                        }
                        mediaPlayer.setDataSource(finalFilePath);
                    }catch (IOException ioe){
                        if(isDebug){
                            ioe.printStackTrace();
                        }
                        //mediaPlayer = null;
                    }
                    mediaPlayer.setDisplay(surfaceHolder);
                    mediaPlayer.prepareAsync();
                    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            if(isDebug){
                                System.out.println("Video is starting...");
                            }

                            // for compatibility, we adjust size based on aspect ratio
                            if ( mp.getVideoWidth() * svLayoutParams.height  < svLayoutParams.width * mp.getVideoHeight() ) {
                                //Log.i("@@@", "image too wide, correcting");
                                svLayoutParams.width = svLayoutParams.height * mp.getVideoWidth() / mp.getVideoHeight();
                            } else if ( mp.getVideoWidth() * svLayoutParams.height  > svLayoutParams.width * mp.getVideoHeight() ) {
                                //Log.i("@@@", "image too tall, correcting");
                                svLayoutParams.height = svLayoutParams.width * mp.getVideoHeight() / mp.getVideoWidth();
                            }
                            sv.post(new Runnable(){
                                    @Override
                                    public void run() {
                                        sv.setLayoutParams(svLayoutParams);
                                    }
                                });


                            mp.start();
                        }
                    });
                }

                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                    if(isDebug){
                        System.out.println("surfaceChanged(holder, "+format+", "+width+", "+height+")");
                    }
                }

                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                    try {
                        mediaPlayer.setDataSource("");
                    }catch (IOException ioe){
                        if(isDebug){
                            ioe.printStackTrace();
                        }
                    }
                }
            });

            if(sv.post(new Runnable() {
                @Override
                public void run() {

                    sv.setLayoutParams(svLayoutParams);///
                    sv.setVisibility(View.VISIBLE);

                }})){

                if(isDebug) {
                    System.out.println("post Succeded");
                }
            }else{
                if(isDebug) {
                    System.out.println("post Failed");
                }
            }
LaruYan
  • 51
  • 1
  • 6
  • 1
    The source code for `VideoView` uses a lot of references to hidden fields and methods of `MediaPlayer`, how did you get around that when you copied it to make your own class? – Tony Chan Feb 20 '15 at 20:54
  • I was stupid that time before pinxue told us MediaPlayer + SurfaceView. :/ I just deleted everything that occurs compiler errors on copied VideoView Source.. most of those was subtitle-related references which I don't need. ..and worked for my purpose well. I also recommend MediaPlayer + SurfaceView approach. – LaruYan Feb 21 '15 at 05:19
  • Thanks for clarifying, I ended up deleting the compile error code as well. Do you happen to know a good tutorial for using the MediaPlayer + SurfaceView approach? I might get around to switching to it eventually. – Tony Chan Feb 24 '15 at 07:04
  • Maybe my code isn't sufficient? I took first approach on http://onycomict.com/board/bbs/board.php?bo_table=class&wr_id=126 (Sorry for not English, it's written in Korean) but this code is nasty(in my view) because it is integrated with Activity, I rewrote whole thing with cherry-picking AR support from VideoView. so then I posted in this SO answer. – LaruYan Feb 25 '15 at 09:55
2

The accepted solution does not guarantee compatibility across all Android versions and is a dirty hack more than a true solution. I've tried all forms of hacks to get this working, yet none have worked to my satisfaction.

I have come up with a much better solution though - switch from a VideoView to a TextureView and load it with a MediaPlayer. There is no difference from the user's perspective, just no more audio stoppage.

Here's my use case for playing an MP4 looping:

private TextureView _introVideoTextureView;
private MediaPlayer _introMediaPlayer;

...

@Override
public void onCreate(...) {
    _introVideoTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
           try {
               destoryIntroVideo();

               _introMediaPlayer = MediaPlayer.create(SignInActivity.this, R.raw.intro_video);
               _introMediaPlayer.setSurface(new Surface(surfaceTexture));
               _introMediaPlayer.setLooping(true);
               _introMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
               _introMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mediaPlayer) {
                        mediaPlayer.start();
                    }
                });

            } catch (Exception e) {
                System.err.println("Error playing intro video: " + e.getMessage());
            }
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {}

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
    });
}

@Override
public void onDestroy() {
    super.onDestroy();

    destoryIntroVideo();
}

private void destoryIntroVideo() {
    if (_introMediaPlayer != null) {
        _introMediaPlayer.stop();
        _introMediaPlayer.release();
        _introMediaPlayer = null;
    }
}
Cord Rehn
  • 1,119
  • 14
  • 22
1

You may use MediaPlayer + SurfaceView instead.

pinxue
  • 1,736
  • 12
  • 17
0

use audioManager.abandonAudioFocus(null)

If you look at the VideoView code you will notice it calls the method audioManager.requestAudioFocus with null for the OnAudioFocusChangeListener. When you register a listener with the AudioManager it uses this method to make an ID for the listener

private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
    if (l == null) {
        return new String(this.toString());
    } else {
        return new String(this.toString() + l.toString());
    }
}

which generates the same ID every time you use null. So if you call abandonAudioFocus with null it will remove any listener that was added with null as the parameter for the OnAudioFocusChangeListener

Ner
  • 3
  • 1