遥かへのスピードランナー

シリコンバレーでAndroidアプリの開発してます。コンピュータービジョン・3D・アルゴリズム界隈にもたまに出現します。

Live Wallpaperで動画ファイルをそのまま表示する(とりあえずできたよ編)

Android OS 2.1からLive Wallpaper(ライブ壁紙)の機能が追加されていますが、いざLive Wallpaperを自分で作る、となると結構面倒です。

実装方法の詳細は、「Android 2.1の新機能「Live Wallpaper」で作る、美しく燃える“待ち受け”」に詳しいですが、WallpaperService.Engineの各種メソッドをオーバーライドして、SurfaceHolderを経由してCanvasに描画処理を行うことで、アニメーションを実現するのが常套手段のようです。

ですがこの方法はかなり面倒です。単にアニメーションを再生したいだけなのに、Javaでゴリゴリプログラムを書きたくない。
そもそも、mp4とかの動画ファイルが素材としてあるんだったらそれをそのまま表示できないのでしょうか?

というわけで色々試してみます。
MediaPlayerクラスで再生している動画はSurfaceHolderに対して表示できるので、まずはその方法で試してみます。

MediaPlayerにSurfaceHolderをそのままsetDiaplayしてやる

public class LiveWallpaperTest extends WallpaperService {
~~
    class LiveEngineTest extends Engine {

        private MediaPlayer mp;

        @Override
        public void onCreate(SurfaceHolder holder) {
            super.onCreate(holder);
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            mp = new MediaPlayer();
            mp.setDisplay(holder);
            mp.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });
            try {
                mp.setDataSource(LiveWallpaperTest.this, Uri.parse("content://media/external/video/media/1"));
            } catch (Exception e) {
                Log.e(TAG, "error");
            }
            mp.setOnCompletionListener(new OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    mp.stop();
                }
            });
        }

        // MediaPlayerをpreparaeする部分は省略

    }
~~
}

こいつを実行してみます。
さてうまく再生できるでしょうか?

ダメでした。logcatには以下のようなエラーが出力されます。

E/AndroidRuntime(  754): java.lang.UnsupportedOperationException: Wallpapers do not support keep screen on
E/AndroidRuntime(  754): 	at android.service.wallpaper.WallpaperService$Engine$2.setKeepScreenOn(WallpaperService.java:202)
E/AndroidRuntime(  754): 	at android.media.MediaPlayer.updateSurfaceScreenOn(MediaPlayer.java:895)
E/AndroidRuntime(  754): 	at android.media.MediaPlayer.setDisplay(MediaPlayer.java:573)

ということで、MediaPlayer.setDisplayの中で自動的に、SurfaceHolder.setKeepScreenOnが呼ばれますが、WallpaperのSurfaceHolderはこのメソッドをサポートしていないのです。

ここで一工夫します。Wallpaper用のSurfaceHolderをそのまま使っていると上記の例外が避けられないので、独自のSurfaceHolderのWrapperを自分でこしらえます。

独自のSurfaceHolder WapperをMediaPlayerにsetDiaplayしてやる

    class MySurfaceHolder implements SurfaceHolder {

        private SurfaceHolder surfaceHolder;

        public MySurfaceHolder(SurfaceHolder surfaceHolder) {
            this.surfaceHolder = surfaceHolder;
        }

        @Override
        public void addCallback(Callback callback) {
            surfaceHolder.addCallback(callback);
        }

        @Override
        public Surface getSurface() {
            return surfaceHolder.getSurface();
        }

        @Override
        public Rect getSurfaceFrame() {
            return surfaceHolder.getSurfaceFrame();
        }

        @Override
        public boolean isCreating() {
            return surfaceHolder.isCreating();
        }

        @Override
        public Canvas lockCanvas() {
            return surfaceHolder.lockCanvas();
        }

        @Override
        public Canvas lockCanvas(Rect dirty) {
            return surfaceHolder.lockCanvas(dirty);
        }

        @Override
        public void removeCallback(Callback callback) {
            surfaceHolder.removeCallback(callback);
        }

        @Override
        public void setFixedSize(int width, int height) {
            surfaceHolder.setFixedSize(width, height);
        }

        @Override
        public void setFormat(int format) {
            surfaceHolder.setFormat(format);
        }

        @Override
        public void setKeepScreenOn(boolean screenOn) {
            return;
        }

        @Override
        public void setSizeFromLayout() {
            surfaceHolder.setSizeFromLayout();
        }

        @Override
        public void setType(int type) {
            surfaceHolder.setType(type);
        }

        @Override
        public void unlockCanvasAndPost(Canvas canvas) {
            surfaceHolder.unlockCanvasAndPost(canvas);
        }
    }

見ての通り、setKeepScreenOnメソッドの実装を空にしている以外は、全てもとのSurfaceHolderに丸投げするだけです。
こいつを使ってもう一度動画を再生してみましょう。

public class LiveWallpaperTest extends WallpaperService {
~~
    class LiveEngineTest extends Engine {

        private MediaPlayer mp;

        @Override
        public void onCreate(SurfaceHolder holder) {
            super.onCreate(holder);
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            mp = new MediaPlayer();
            mp.setDisplay(new MySurfaceHolder(holder));
            mp.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });
            try {
                mp.setDataSource(LiveWallpaperTest.this, Uri.parse("content://media/external/video/media/1"));
            } catch (Exception e) {
                Log.e(TAG, "error");
            }
            mp.setOnCompletionListener(new OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    mp.stop();
                }
            });
        }

        @Override
		public void onDestroy() {
			super.onDestroy();
			if (mp != null) {
                mp.stop();
                mp.release();
            }
		}

        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);
            if (visible) {
                play();
            }
        }

        private void play() {
            if (mp.isPlaying()) {
                mp.stop();
            }
            try {
                mp.prepareAsync();
            } catch (IllegalArgumentException e) {
            	Log.e(TAG, "error");
            } catch (SecurityException e) {
            	Log.e(TAG, "error");
            } catch (IllegalStateException e) {
            	Log.e(TAG, "error");
            }
        }
    }
~~
}

さてどうでしょうか?

見事、Live Wallpaperとして動画が表示されています!

ソースコード

githubにて公開しています。

から持っていってください。

まとめ

  • Live wallpaperのSurfaceHolderにMediaPlayerで再生した動画を表示しようとするとエラーでこける。
  • SurfaceHolderをそのまま使うんじゃなくて、独自のWrapperを用意すればMediaPlayerで再生した動画をそのままライブ壁紙に使える。

という話でした。

単に動画を表示するだけのライブ壁紙はつまらないですが、面白いアプリを作る一手段としてなら使えるんじゃないでしょうか。(電池をどれくらい食うか心配ですが。。)