From 95f6995a7204e19b00625454ba93a350683feebc Mon Sep 17 00:00:00 2001 From: Chirag Maheshwari Date: Tue, 22 Aug 2017 15:03:37 +0530 Subject: [PATCH 1/3] Chromecast support (#266) * resolve TransactionTooLargeException on android 7 * adds chromecast support * adds cast session manager and remote media to native player * adds ExpandedController for cast * resolve blank video activity on stopping cast * adds mini controller * adds cast notifications and change app id * resolve crash on opening cast activity through notification * adds cast support for audio files * start casting only after metadata is fetched * adds fixed image audio cast * shift chromecast app_id in api.properties --- build.gradle | 3 + fakeApi.properties | 1 + src/main/AndroidManifest.xml | 16 +- .../activity/ExpandedControlsActivity.java | 35 ++ .../activity/NativeVideoActivity.java | 211 ++++++++++-- .../activity/ServerFileAudioActivity.java | 312 +++++++++++++----- .../activity/ServerFilesActivity.java | 6 +- .../bus/AudioMetadataRetrievedEvent.java | 11 +- .../fragment/ServerFilesFragment.java | 61 +++- .../amahi/anywhere/service/AudioService.java | 4 +- .../task/AudioMetadataRetrievingTask.java | 6 +- .../anywhere/util/AudioMetadataFormatter.java | 17 + .../anywhere/util/CastOptionsProvider.java | 61 ++++ src/main/res/layout/activity_server_files.xml | 20 +- src/main/res/menu/action_bar_cast_button.xml | 31 ++ .../menu/action_bar_expanded_controller.xml | 30 ++ src/main/res/menu/action_bar_server_files.xml | 12 +- src/main/res/values/strings.xml | 3 + src/main/res/values/themes.xml | 4 + 19 files changed, 710 insertions(+), 134 deletions(-) create mode 100644 src/main/java/org/amahi/anywhere/activity/ExpandedControlsActivity.java create mode 100644 src/main/java/org/amahi/anywhere/util/CastOptionsProvider.java create mode 100644 src/main/res/menu/action_bar_cast_button.xml create mode 100644 src/main/res/menu/action_bar_expanded_controller.xml diff --git a/build.gradle b/build.gradle index 0d1723e8d..8324ba52e 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,7 @@ android { buildConfigField "String", "API_URL_PROXY", formatStringField(apiProperties["url.proxy"]) buildConfigField "String", "API_CLIENT_ID", formatStringField(apiProperties["client.id"]) buildConfigField "String", "API_CLIENT_SECRET", formatStringField(apiProperties["client.secret"]) + buildConfigField "String", "CHROMECAST_APP_ID", formatStringField(apiProperties["chromecast.app.id"]) testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -116,6 +117,8 @@ dependencies { compile "com.android.support:preference-v7:${SUPPORT_LIBRARY_VERSION}" compile "com.android.support:customtabs:${SUPPORT_LIBRARY_VERSION}" compile "com.android.support:leanback-v17:${SUPPORT_LIBRARY_VERSION}" + compile "com.android.support:mediarouter-v7:${SUPPORT_LIBRARY_VERSION}" + compile 'com.google.android.gms:play-services-cast-framework:11.0.2' compile('com.crashlytics.sdk.android:crashlytics:2.6.6@aar') { transitive = true; } diff --git a/fakeApi.properties b/fakeApi.properties index 55224af28..6ee986e3f 100644 --- a/fakeApi.properties +++ b/fakeApi.properties @@ -6,3 +6,4 @@ url.amahi=https://amahi.org url.proxy=https://amahi.org client.id=1234567890 client.secret=abcdefghijklmnopqrstuvwxyz +chromecast.app.id=CC1AD845 \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index e0fc1ab2a..495426571 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -100,9 +100,19 @@ + + + + + + android:theme="@style/Theme.Amahi.Fullscreen" + android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"/> + + diff --git a/src/main/java/org/amahi/anywhere/activity/ExpandedControlsActivity.java b/src/main/java/org/amahi/anywhere/activity/ExpandedControlsActivity.java new file mode 100644 index 000000000..eb15ec857 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/activity/ExpandedControlsActivity.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.amahi.anywhere.activity; + +import android.view.Menu; + +import com.google.android.gms.cast.framework.CastButtonFactory; +import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity; + +import org.amahi.anywhere.R; + +public class ExpandedControlsActivity extends ExpandedControllerActivity { + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.action_bar_expanded_controller, menu); + CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/amahi/anywhere/activity/NativeVideoActivity.java b/src/main/java/org/amahi/anywhere/activity/NativeVideoActivity.java index 5a1ecc0d3..1f70a5557 100644 --- a/src/main/java/org/amahi/anywhere/activity/NativeVideoActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/NativeVideoActivity.java @@ -19,16 +19,26 @@ package org.amahi.anywhere.activity; +import android.content.Intent; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.VideoView; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.cast.framework.CastButtonFactory; +import com.google.android.gms.cast.framework.CastContext; +import com.google.android.gms.cast.framework.CastSession; +import com.google.android.gms.cast.framework.SessionManagerListener; +import com.google.android.gms.cast.framework.media.RemoteMediaClient; + import org.amahi.anywhere.AmahiApplication; import org.amahi.anywhere.R; import org.amahi.anywhere.server.client.ServerClient; @@ -51,7 +61,9 @@ */ public class NativeVideoActivity extends AppCompatActivity implements MediaPlayer.OnPreparedListener, - MediaPlayer.OnCompletionListener { + MediaPlayer.OnCompletionListener, + SessionManagerListener +{ private static final Set SUPPORTED_FORMATS; private static final String VIDEO_POSITION = "video_position"; @@ -76,6 +88,9 @@ public static boolean supports(String mime_type) { private MediaControls videoControls; private VideoView videoView; + private CastContext mCastContext; + private CastSession mCastSession; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -88,6 +103,8 @@ protected void onCreate(Bundle savedInstanceState) { setUpHomeNavigation(); + setUpCast(); + setUpVideo(); setUpFullScreen(); @@ -102,9 +119,20 @@ private void setUpHomeNavigation() { getSupportActionBar().setIcon(R.drawable.ic_launcher); } + private void setUpCast() { + mCastContext = CastContext.getSharedInstance(this); + mCastSession = mCastContext.getSessionManager().getCurrentCastSession(); + } + private void setUpVideo() { - setUpVideoTitle(); - setUpVideoView(); + if (mCastSession != null && mCastSession.isConnected()) { + loadRemoteMedia(0, true); + finish(); + } else { + setUpVideoTitle(); + setUpVideoView(); + startVideo(); + } } private ServerShare getVideoShare() { @@ -127,23 +155,30 @@ private View getControlsContainer() { return findViewById(R.id.container_controls); } - private FrameLayout getVideoSurfaceFrame() { - return (FrameLayout) findViewById(R.id.video_surface_frame); - } - private ProgressBar getProgressBar() { return (ProgressBar) findViewById(android.R.id.progress); } - private void setUpVideoView() { - setUpVideoControls(); - videoView = (VideoView) findViewById(R.id.video_view); - videoView.setOnPreparedListener(this); - videoView.setOnCompletionListener(this); - videoView.setVideoURI(getVideoUri()); - videoView.setMediaController(videoControls); - videoView.start(); - } + private void setUpVideoView() { + videoView = (VideoView) findViewById(R.id.video_view); + setUpVideoControls(); + videoView.setOnPreparedListener(this); + videoView.setOnCompletionListener(this); + videoView.setVideoURI(getVideoUri()); + videoView.setMediaController(videoControls); + } + + private void startVideo() { + Bundle bundle = getIntent().getExtras(); + boolean shouldStartPlayback = bundle.getBoolean("shouldStart", true); + int startPosition = bundle.getInt("startPosition", 0); + if (startPosition > 0) { + videoView.seekTo(startPosition); + } + if (shouldStartPlayback) { + videoView.start(); + } + } private boolean areVideoControlsAvailable() { return videoControls != null; @@ -164,7 +199,16 @@ private void setUpFullScreen() { fullScreen.init(); } - @Override + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.action_bar_cast_button, menu); + CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, + R.id.media_route_menu_item); + return true; + } + + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case android.R.id.home: @@ -176,25 +220,31 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { } } - @Override - protected void onStart() { - super.onStart(); + @Override + protected void onResume() { + mCastContext.getSessionManager().addSessionManagerListener(this, CastSession.class); + super.onResume(); + } - } - - @Override + @Override public void onPause() { super.onPause(); - videoControls.hide(); + mCastContext.getSessionManager().removeSessionManagerListener(this, CastSession.class); - if (!isChangingConfigurations()) { - videoView.pause(); - } + if (videoControls != null && videoControls.isShowing()) { + videoControls.hide(); + } - if (isFinishing()) { - videoView.stopPlayback(); - } + if (videoView != null) { + if (!isChangingConfigurations()) { + videoView.pause(); + } + + if (isFinishing()) { + videoView.stopPlayback(); + } + } } @Override @@ -206,14 +256,16 @@ public void onPrepared(MediaPlayer mp) { @Override protected void onSaveInstanceState(Bundle outState) { - outState.putInt(VIDEO_POSITION, videoView.getCurrentPosition()); + if (videoView != null) { + outState.putInt(VIDEO_POSITION, videoView.getCurrentPosition()); + } super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { int videoPosition = savedInstanceState.getInt(VIDEO_POSITION, 0); - if (videoPosition > 0) { + if (videoPosition > 0 && videoView != null) { videoView.seekTo(videoPosition); } super.onRestoreInstanceState(savedInstanceState); @@ -223,4 +275,99 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) { public void onCompletion(MediaPlayer mp) { finish(); } + + @Override + public void onSessionEnded(CastSession session, int error) {} + + @Override + public void onSessionResumed(CastSession session, boolean wasSuspended) { + onApplicationConnected(session); + } + + @Override + public void onSessionResumeFailed(CastSession session, int error) {} + + @Override + public void onSessionStarted(CastSession session, String sessionId) { + onApplicationConnected(session); + } + + @Override + public void onSessionStartFailed(CastSession session, int error) {} + + @Override + public void onSessionStarting(CastSession session) {} + + @Override + public void onSessionEnding(CastSession session) {} + + @Override + public void onSessionResuming(CastSession session, String sessionId) {} + + @Override + public void onSessionSuspended(CastSession session, int reason) {} + + private void onApplicationConnected(CastSession castSession) { + mCastSession = castSession; + boolean isVideoPlaying = videoView.isPlaying(); + if (isVideoPlaying) + videoView.pause(); + loadRemoteMedia(videoView.getCurrentPosition(), isVideoPlaying); + finish(); + } + + private void loadRemoteMedia(int position, boolean autoPlay) { + if (mCastSession == null) { + return; + } + final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); + if (remoteMediaClient == null) { + return; + } + remoteMediaClient.addListener(new RemoteMediaClient.Listener() { + @Override + public void onStatusUpdated() { + Intent intent = new Intent(NativeVideoActivity.this, ExpandedControlsActivity.class); + startActivity(intent); + remoteMediaClient.removeListener(this); + } + + @Override + public void onMetadataUpdated() { + } + + @Override + public void onQueueStatusUpdated() { + } + + @Override + public void onPreloadStatusUpdated() { + } + + @Override + public void onSendingRemoteMediaRequest() { + } + + @Override + public void onAdBreakStatusUpdated() { + + } + }); + remoteMediaClient.load(buildMediaInfo(), autoPlay, position); + } + + private MediaInfo buildMediaInfo() { + MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); + + movieMetadata.putString(MediaMetadata.KEY_TITLE, getVideoFile().getNameOnly()); + + MediaInfo.Builder builder = new MediaInfo.Builder(getVideoUri().toString()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(getVideoFile().getMime()) + .setMetadata(movieMetadata); + if (videoView != null) { + builder.setStreamDuration(videoView.getDuration()); + } + return builder.build(); + } } diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java index b0cb0d94d..2a0964115 100644 --- a/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java @@ -25,16 +25,25 @@ import android.content.ServiceConnection; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.support.v7.app.AppCompatActivity; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.MediaController; import android.widget.TextView; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.cast.framework.CastButtonFactory; +import com.google.android.gms.cast.framework.CastContext; +import com.google.android.gms.cast.framework.CastSession; +import com.google.android.gms.cast.framework.SessionManagerListener; +import com.google.android.gms.cast.framework.media.RemoteMediaClient; +import com.google.android.gms.common.images.WebImage; import com.squareup.otto.Subscribe; import org.amahi.anywhere.AmahiApplication; @@ -49,6 +58,7 @@ import org.amahi.anywhere.server.model.ServerFile; import org.amahi.anywhere.server.model.ServerShare; import org.amahi.anywhere.service.AudioService; +import org.amahi.anywhere.task.AudioMetadataRetrievingTask; import org.amahi.anywhere.util.AudioMetadataFormatter; import org.amahi.anywhere.util.Intents; import org.amahi.anywhere.util.ViewDirector; @@ -67,31 +77,34 @@ * The playback itself is done via {@link org.amahi.anywhere.service.AudioService}. * Backed up by {@link android.media.MediaPlayer}. */ -public class ServerFileAudioActivity extends AppCompatActivity implements ServiceConnection, MediaController.MediaPlayerControl -{ +public class ServerFileAudioActivity extends AppCompatActivity implements + ServiceConnection, + MediaController.MediaPlayerControl, + SessionManagerListener { + private CastContext mCastContext; + private CastSession mCastSession; + private AudioMetadataFormatter metadataFormatter; + private static final Set SUPPORTED_FORMATS; static { - SUPPORTED_FORMATS = new HashSet(Arrays.asList( - "audio/flac", - "audio/mp4", - "audio/mpeg", - "audio/ogg" + SUPPORTED_FORMATS = new HashSet<>(Arrays.asList( + "audio/flac", + "audio/mp4", + "audio/mpeg", + "audio/ogg" )); } - public static boolean supports(String mime_type) { - return SUPPORTED_FORMATS.contains(mime_type); - } + private PlaybackLocation mLocation = PlaybackLocation.LOCAL; - private static final class State - { - private State() { - } + public enum PlaybackLocation { + LOCAL, + REMOTE + } - public static final String AUDIO_TITLE = "audio_title"; - public static final String AUDIO_SUBTITLE = "audio_subtitle"; - public static final String AUDIO_ALBUM_ART = "audio_album_art"; + public static boolean supports(String mime_type) { + return SUPPORTED_FORMATS.contains(mime_type); } @Inject @@ -111,7 +124,9 @@ protected void onCreate(Bundle savedInstanceState) { setUpHomeNavigation(); - setUpAudio(savedInstanceState); + setUpCast(); + + setUpAudio(); } private void setUpInjections() { @@ -123,10 +138,17 @@ private void setUpHomeNavigation() { getSupportActionBar().setIcon(R.drawable.ic_launcher); } - private void setUpAudio(Bundle state) { + private void setUpCast() { + mCastContext = CastContext.getSharedInstance(this); + mCastSession = mCastContext.getSessionManager().getCurrentCastSession(); + if (mCastSession != null && mCastSession.isConnected()) { + mLocation = PlaybackLocation.REMOTE; + } + } + + private void setUpAudio() { setUpAudioFile(); setUpAudioTitle(); - setUpAudioMetadata(state); } private void setUpAudioFile() { @@ -141,24 +163,6 @@ private void setUpAudioTitle() { getSupportActionBar().setTitle(audioFile.getName()); } - private void setUpAudioMetadata(Bundle state) { - if (isAudioMetadataStateValid(state)) { - setUpAudioMetadataState(state); - - showAudioMetadata(); - } - } - - private boolean isAudioMetadataStateValid(Bundle state) { - return (state != null) && state.containsKey(State.AUDIO_TITLE); - } - - private void setUpAudioMetadataState(Bundle state) { - getAudioTitleView().setText(state.getString(State.AUDIO_TITLE)); - getAudioSubtitleView().setText(state.getString(State.AUDIO_SUBTITLE)); - getAudioAlbumArtView().setImageBitmap((Bitmap) state.getParcelable(State.AUDIO_ALBUM_ART)); - } - private TextView getAudioTitleView() { return (TextView) findViewById(R.id.text_title); } @@ -177,10 +181,15 @@ private void showAudioMetadata() { @Subscribe public void onAudioMetadataRetrieved(AudioMetadataRetrievedEvent event) { - AudioMetadataFormatter audioMetadataFormatter = new AudioMetadataFormatter( - event.getAudioTitle(), event.getAudioArtist(), event.getAudioAlbum()); - - setUpAudioMetadata(audioMetadataFormatter, event.getAudioAlbumArt()); + metadataFormatter = new AudioMetadataFormatter( + event.getAudioTitle(), event.getAudioArtist(), event.getAudioAlbum()); + metadataFormatter.setDuration(event.getDuration()); + if (mLocation == PlaybackLocation.LOCAL) { + setUpAudioMetadata(metadataFormatter, event.getAudioAlbumArt()); + } else if (mLocation == PlaybackLocation.REMOTE) { + loadRemoteMedia(0, true); + finish(); + } } private void setUpAudioMetadata(AudioMetadataFormatter audioMetadataFormatter, Bitmap audioAlbumArt) { @@ -199,12 +208,20 @@ private ServerShare getShare() { return getIntent().getParcelableExtra(Intents.Extras.SERVER_SHARE); } + private Uri getAudioUri() { + return serverClient.getFileUri(getShare(), getFile()); + } + @Override protected void onStart() { super.onStart(); - setUpAudioService(); - setUpAudioServiceBind(); + if (mLocation == PlaybackLocation.LOCAL) { + setUpAudioService(); + setUpAudioServiceBind(); + } else if (mLocation == PlaybackLocation.REMOTE) { + AudioMetadataRetrievingTask.execute(getAudioUri(), audioFile); + } } private void setUpAudioService() { @@ -251,6 +268,7 @@ private boolean areAudioControlsAvailable() { private void setUpAudioPlayback() { if (audioService.isAudioStarted()) { showAudio(); + setUpAudioMetadata(); } else { audioService.startAudio(getShare(), getAudioFiles(), getFile()); } @@ -398,6 +416,15 @@ public int getAudioSessionId() { return audioService.getAudioPlayer().getAudioSessionId(); } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.action_bar_cast_button, menu); + CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, + R.id.media_route_menu_item); + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { @@ -414,11 +441,15 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { protected void onResume() { super.onResume(); + mCastContext.getSessionManager().addSessionManagerListener(this, CastSession.class); + showAudioControlsForced(); BusProvider.getBus().register(this); - setUpAudioMetadata(); + if (hasAudioFileChanged()) { + setUpAudioMetadata(); + } } private void showAudioControlsForced() { @@ -427,20 +458,23 @@ private void showAudioControlsForced() { } } + private boolean hasAudioFileChanged() { + return isAudioServiceAvailable() && !this.audioFile.equals(audioService.getAudioFile()); + } + private void setUpAudioMetadata() { if (!isAudioServiceAvailable()) { return; } - if (!this.audioFile.equals(audioService.getAudioFile())) { - this.audioFile = audioService.getAudioFile(); + metadataFormatter = audioService.getAudioMetadataFormatter(); + this.audioFile = audioService.getAudioFile(); - tearDownAudioTitle(); - tearDownAudioMetadata(); + tearDownAudioTitle(); + tearDownAudioMetadata(); - setUpAudioTitle(); - setUpAudioMetadata(audioService.getAudioMetadataFormatter(), audioService.getAudioAlbumArt()); - } + setUpAudioTitle(); + setUpAudioMetadata(audioService.getAudioMetadataFormatter(), audioService.getAudioAlbumArt()); } private boolean isAudioServiceAvailable() { @@ -451,11 +485,13 @@ private boolean isAudioServiceAvailable() { protected void onPause() { super.onPause(); + mCastContext.getSessionManager().removeSessionManagerListener(this, CastSession.class); + hideAudioControlsForced(); BusProvider.getBus().unregister(this); - if (isFinishing()) { + if (isAudioServiceAvailable() && isFinishing()) { tearDownAudioPlayback(); } } @@ -474,45 +510,20 @@ private void tearDownAudioPlayback() { protected void onStop() { super.onStop(); - tearDownAudioServiceBind(); + if (isAudioServiceAvailable()) { + tearDownAudioServiceBind(); + } } private void tearDownAudioServiceBind() { unbindService(this); } - @Override - protected void onSaveInstanceState(Bundle state) { - super.onSaveInstanceState(state); - - if (isAudioMetadataLoaded()) { - tearDownAudioMetadataState(state); - } - } - - private boolean isAudioMetadataLoaded() { - String audioTitle = getAudioTitleView().getText().toString(); - String audioSubtitle = getAudioSubtitleView().getText().toString(); - BitmapDrawable audioAlbumArt = (BitmapDrawable) getAudioAlbumArtView().getDrawable(); - - return !audioTitle.isEmpty() && !audioSubtitle.isEmpty() && (audioAlbumArt != null); - } - - private void tearDownAudioMetadataState(Bundle state) { - String audioTitle = getAudioTitleView().getText().toString(); - String audioSubtitle = getAudioSubtitleView().getText().toString(); - BitmapDrawable audioAlbumArt = (BitmapDrawable) getAudioAlbumArtView().getDrawable(); - - state.putString(State.AUDIO_TITLE, audioTitle); - state.putString(State.AUDIO_SUBTITLE, audioSubtitle); - state.putParcelable(State.AUDIO_ALBUM_ART, audioAlbumArt.getBitmap()); - } - @Override protected void onDestroy() { super.onDestroy(); - if (isFinishing()) { + if (isAudioServiceAvailable() && isFinishing()) { tearDownAudioService(); } } @@ -522,19 +533,146 @@ private void tearDownAudioService() { stopService(intent); } - private static final class AudioControlsNextListener implements View.OnClickListener - { + private static final class AudioControlsNextListener implements View.OnClickListener { @Override public void onClick(View view) { BusProvider.getBus().post(new AudioControlNextEvent()); } } - private static final class AudioControlsPreviousListener implements View.OnClickListener - { + private static final class AudioControlsPreviousListener implements View.OnClickListener { @Override public void onClick(View view) { BusProvider.getBus().post(new AudioControlPreviousEvent()); } } + + @Override + public void onSessionEnded(CastSession session, int error) { + onApplicationDisconnected(); + } + + @Override + public void onSessionResumed(CastSession session, boolean wasSuspended) { + onApplicationConnected(session); + } + + @Override + public void onSessionResumeFailed(CastSession session, int error) { + onApplicationDisconnected(); + } + + @Override + public void onSessionStarted(CastSession session, String sessionId) { + onApplicationConnected(session); + } + + @Override + public void onSessionStartFailed(CastSession session, int error) { + onApplicationDisconnected(); + } + + @Override + public void onSessionStarting(CastSession session) { + } + + @Override + public void onSessionEnding(CastSession session) { + } + + @Override + public void onSessionResuming(CastSession session, String sessionId) { + } + + @Override + public void onSessionSuspended(CastSession session, int reason) { + } + + private void onApplicationConnected(CastSession castSession) { + mCastSession = castSession; + boolean isPlaying = false; + int position = 0; + if (audioService != null) { + isPlaying = audioService.isAudioStarted(); + if (isPlaying) { + audioService.pauseAudio(); + position = audioService.getAudioPlayer().getCurrentPosition(); + } + } + loadRemoteMedia(position, isPlaying); + finish(); + } + + private void onApplicationDisconnected() { + mCastSession = null; + mLocation = PlaybackLocation.LOCAL; + invalidateOptionsMenu(); + if (!isAudioServiceAvailable()) { + setUpAudioService(); + setUpAudioServiceBind(); + } + } + + private void loadRemoteMedia(int position, boolean autoPlay) { + if (mCastSession == null) { + return; + } + final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); + if (remoteMediaClient == null) { + return; + } + remoteMediaClient.addListener(new RemoteMediaClient.Listener() { + @Override + public void onStatusUpdated() { + Intent intent = new Intent(ServerFileAudioActivity.this, ExpandedControlsActivity.class); + startActivity(intent); + remoteMediaClient.removeListener(this); + } + + @Override + public void onMetadataUpdated() { + } + + @Override + public void onQueueStatusUpdated() { + } + + @Override + public void onPreloadStatusUpdated() { + } + + @Override + public void onSendingRemoteMediaRequest() { + } + + @Override + public void onAdBreakStatusUpdated() { + } + }); + remoteMediaClient.load(buildMediaInfo(), autoPlay, position); + } + + private MediaInfo buildMediaInfo() { + MediaMetadata audioMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK); + + if (metadataFormatter != null) { + audioMetadata.putString(MediaMetadata.KEY_TITLE, metadataFormatter.getAudioTitle(getFile())); + audioMetadata.putString(MediaMetadata.KEY_ARTIST, metadataFormatter.getAudioArtist()); + audioMetadata.putString(MediaMetadata.KEY_ALBUM_TITLE, metadataFormatter.getAudioAlbum()); + } else { + audioMetadata.putString(MediaMetadata.KEY_TITLE, getFile().getNameOnly()); + } + + audioMetadata.addImage(new WebImage(Uri.parse("http://alpha.amahi.org/cast/audio-play.jpg"))); + + String audioSource = serverClient.getFileUri(getShare(), getFile()).toString(); + MediaInfo.Builder builder = new MediaInfo.Builder(audioSource) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(getFile().getMime()) + .setMetadata(audioMetadata); + if (metadataFormatter != null) { + builder.setStreamDuration(metadataFormatter.getDuration()); + } + return builder.build(); + } } diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java index 781d21d46..f0f0f3c3e 100644 --- a/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java @@ -20,10 +20,10 @@ package org.amahi.anywhere.activity; import android.app.DialogFragment; -import android.support.v4.app.Fragment; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; @@ -35,8 +35,8 @@ import org.amahi.anywhere.bus.FileDownloadedEvent; import org.amahi.anywhere.bus.FileOpeningEvent; import org.amahi.anywhere.bus.ServerFileSharingEvent; -import org.amahi.anywhere.fragment.ServerFileDownloadingFragment; import org.amahi.anywhere.fragment.GooglePlaySearchFragment; +import org.amahi.anywhere.fragment.ServerFileDownloadingFragment; import org.amahi.anywhere.server.client.ServerClient; import org.amahi.anywhere.server.model.ServerFile; import org.amahi.anywhere.server.model.ServerShare; @@ -64,7 +64,7 @@ private State() { public static final String FILE_ACTION = "file_action"; } - private static enum FileAction + private enum FileAction { OPEN, SHARE } diff --git a/src/main/java/org/amahi/anywhere/bus/AudioMetadataRetrievedEvent.java b/src/main/java/org/amahi/anywhere/bus/AudioMetadataRetrievedEvent.java index 9f90b2a30..3febcdd27 100644 --- a/src/main/java/org/amahi/anywhere/bus/AudioMetadataRetrievedEvent.java +++ b/src/main/java/org/amahi/anywhere/bus/AudioMetadataRetrievedEvent.java @@ -28,14 +28,19 @@ public class AudioMetadataRetrievedEvent implements BusEvent { private final String audioTitle; private final String audioArtist; private final String audioAlbum; + private final long duration; private final Bitmap audioAlbumArt; private final MainTVPresenter.ViewHolder viewHolder; private final ServerFile serverFile; - public AudioMetadataRetrievedEvent(String audioTitle, String audioArtist, String audioAlbum, Bitmap audioAlbumArt, MainTVPresenter.ViewHolder viewHolder, ServerFile serverFile) { + public AudioMetadataRetrievedEvent(String audioTitle, String audioArtist, String audioAlbum, + String duration, Bitmap audioAlbumArt, + MainTVPresenter.ViewHolder viewHolder, + ServerFile serverFile) { this.audioTitle = audioTitle; this.audioArtist = audioArtist; this.audioAlbum = audioAlbum; + this.duration = Long.valueOf(duration); this.audioAlbumArt = audioAlbumArt; this.viewHolder = viewHolder; this.serverFile = serverFile; @@ -64,4 +69,8 @@ public MainTVPresenter.ViewHolder getViewHolder() { public ServerFile getServerFile() { return serverFile; } + + public long getDuration() { + return duration; + } } diff --git a/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java b/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java index d17d4ea78..8486de8b3 100644 --- a/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java +++ b/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java @@ -27,6 +27,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.Parcelable; import android.provider.Settings; import android.support.annotation.RequiresApi; @@ -48,6 +49,11 @@ import android.widget.ListAdapter; import android.widget.TextView; +import com.google.android.gms.cast.framework.CastButtonFactory; +import com.google.android.gms.cast.framework.CastContext; +import com.google.android.gms.cast.framework.CastState; +import com.google.android.gms.cast.framework.CastStateListener; +import com.google.android.gms.cast.framework.IntroductoryOverlay; import com.squareup.otto.Subscribe; import org.amahi.anywhere.AmahiApplication; @@ -81,16 +87,21 @@ /** * Files fragment. Shows files list. */ -public class ServerFilesFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, +public class ServerFilesFragment extends Fragment implements + SwipeRefreshLayout.OnRefreshListener, AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener, ActionMode.Callback, SearchView.OnQueryTextListener, - FilesFilterBaseAdapter.onFilterListChange + FilesFilterBaseAdapter.onFilterListChange, + CastStateListener { private SearchView searchView; private MenuItem searchMenuItem; private LinearLayout mErrorLinearLayout; + private CastContext mCastContext; + private IntroductoryOverlay mIntroductoryOverlay; + private MenuItem mediaRouteMenuItem; private static final class State { @@ -133,6 +144,8 @@ public void onActivityCreated(Bundle savedInstanceState) { setUpInjections(); + setUpCast(); + setUpFiles(savedInstanceState); } @@ -140,6 +153,44 @@ private void setUpInjections() { AmahiApplication.from(getActivity()).inject(this); } + private void setUpCast() { + mCastContext = CastContext.getSharedInstance(getActivity()); + } + + + @Override + public void onCastStateChanged(int newState) { + if (newState != CastState.NO_DEVICES_AVAILABLE) { + showIntroductoryOverlay(); + } + } + + private void showIntroductoryOverlay() { + if (mIntroductoryOverlay != null) { + mIntroductoryOverlay.remove(); + } + if ((mediaRouteMenuItem != null) && mediaRouteMenuItem.isVisible()) { + new Handler().post(new Runnable() { + @Override + public void run() { + mIntroductoryOverlay = new IntroductoryOverlay + .Builder(getActivity(), mediaRouteMenuItem) + .setTitleText("Introducing Cast") + .setSingleTime() + .setOnOverlayDismissedListener( + new IntroductoryOverlay.OnOverlayDismissedListener() { + @Override + public void onOverlayDismissed() { + mIntroductoryOverlay = null; + } + }) + .build(); + mIntroductoryOverlay.show(); + } + }); + } + } + private void setUpFiles(Bundle state) { setUpFilesMenu(); setUpFilesActions(); @@ -511,6 +562,10 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { super.onCreateOptionsMenu(menu, menuInflater); menuInflater.inflate(R.menu.action_bar_server_files, menu); + + mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton( + getActivity().getApplicationContext(), + menu, R.id.media_route_menu_item); } @Override @@ -628,6 +683,7 @@ private void collapseSearchView() { public void onResume() { super.onResume(); + mCastContext.addCastStateListener(this); BusProvider.getBus().register(this); } @@ -635,6 +691,7 @@ public void onResume() { public void onPause() { super.onPause(); + mCastContext.removeCastStateListener(this); BusProvider.getBus().unregister(this); } diff --git a/src/main/java/org/amahi/anywhere/service/AudioService.java b/src/main/java/org/amahi/anywhere/service/AudioService.java index 9775ffc3a..501be830c 100644 --- a/src/main/java/org/amahi/anywhere/service/AudioService.java +++ b/src/main/java/org/amahi/anywhere/service/AudioService.java @@ -75,7 +75,8 @@ * Places information at {@link android.app.Notification} and {@link MediaSessionCompat}, * handles audio focus changes as well. */ -public class AudioService extends MediaBrowserServiceCompat implements AudioManager.OnAudioFocusChangeListener, +public class AudioService extends MediaBrowserServiceCompat implements + AudioManager.OnAudioFocusChangeListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener @@ -229,6 +230,7 @@ private void setUpAudioMetadata() { public void onAudioMetadataRetrieved(AudioMetadataRetrievedEvent event) { this.audioMetadataFormatter = new AudioMetadataFormatter( event.getAudioTitle(), event.getAudioArtist(), event.getAudioAlbum()); + this.audioMetadataFormatter.setDuration(event.getDuration()); this.audioAlbumArt = event.getAudioAlbumArt(); setUpAudioPlayerRemote(audioMetadataFormatter, audioAlbumArt); diff --git a/src/main/java/org/amahi/anywhere/task/AudioMetadataRetrievingTask.java b/src/main/java/org/amahi/anywhere/task/AudioMetadataRetrievingTask.java index 597ba6f9b..6de356c2d 100644 --- a/src/main/java/org/amahi/anywhere/task/AudioMetadataRetrievingTask.java +++ b/src/main/java/org/amahi/anywhere/task/AudioMetadataRetrievingTask.java @@ -72,11 +72,13 @@ protected BusEvent doInBackground(Void... parameters) { String audioTitle = audioMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); String audioArtist = audioMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); String audioAlbum = audioMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); + String duration = audioMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); Bitmap audioAlbumArt = extractAlbumArt(audioMetadataRetriever); - return new AudioMetadataRetrievedEvent(audioTitle, audioArtist, audioAlbum, audioAlbumArt, viewHolder, serverFile); + return new AudioMetadataRetrievedEvent(audioTitle, audioArtist, audioAlbum, + duration, audioAlbumArt, viewHolder, serverFile); } catch (RuntimeException e) { - return new AudioMetadataRetrievedEvent(null, null, null, null, viewHolder, serverFile); + return new AudioMetadataRetrievedEvent(null, null, null, null, null, viewHolder, serverFile); } finally { audioMetadataRetriever.release(); } diff --git a/src/main/java/org/amahi/anywhere/util/AudioMetadataFormatter.java b/src/main/java/org/amahi/anywhere/util/AudioMetadataFormatter.java index 48d0997a2..778f2a5e8 100644 --- a/src/main/java/org/amahi/anywhere/util/AudioMetadataFormatter.java +++ b/src/main/java/org/amahi/anywhere/util/AudioMetadataFormatter.java @@ -32,6 +32,7 @@ public final class AudioMetadataFormatter { private final String audioTitle; private final String audioArtist; private final String audioAlbum; + private long duration; public AudioMetadataFormatter(String audioTitle, String audioArtist, String audioAlbum) { this.audioTitle = audioTitle; @@ -39,6 +40,14 @@ public AudioMetadataFormatter(String audioTitle, String audioArtist, String audi this.audioAlbum = audioAlbum; } + public void setDuration(long duration) { + this.duration = duration; + } + + public long getDuration() { + return duration; + } + public String getAudioTitle(ServerFile audioFile) { if (TextUtils.isEmpty(audioTitle)) { return audioFile.getName(); @@ -70,4 +79,12 @@ public String getAudioSubtitle(ServerShare audioShare) { return String.format("%s - %s", audioArtist, audioAlbum); } + + public String getAudioArtist() { + return audioArtist; + } + + public String getAudioAlbum() { + return audioAlbum; + } } diff --git a/src/main/java/org/amahi/anywhere/util/CastOptionsProvider.java b/src/main/java/org/amahi/anywhere/util/CastOptionsProvider.java new file mode 100644 index 000000000..73dd17325 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/util/CastOptionsProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.util; + +import android.content.Context; + +import com.google.android.gms.cast.framework.CastOptions; +import com.google.android.gms.cast.framework.OptionsProvider; +import com.google.android.gms.cast.framework.SessionProvider; +import com.google.android.gms.cast.framework.media.CastMediaOptions; +import com.google.android.gms.cast.framework.media.NotificationOptions; + +import org.amahi.anywhere.BuildConfig; +import org.amahi.anywhere.activity.ExpandedControlsActivity; + +import java.util.List; + +/** + * Cast options provider helper class + */ +class CastOptionsProvider implements OptionsProvider { + + @Override + public CastOptions getCastOptions(Context appContext) { + NotificationOptions notificationOptions = new NotificationOptions.Builder() + .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) + .build(); + CastMediaOptions mediaOptions = new CastMediaOptions.Builder() + .setNotificationOptions(notificationOptions) + .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) + .build(); + + return new CastOptions.Builder() + .setReceiverApplicationId(BuildConfig.CHROMECAST_APP_ID) + .setCastMediaOptions(mediaOptions) + .build(); + } + + @Override + public List getAdditionalSessionProviders(Context context) { + return null; + } + +} diff --git a/src/main/res/layout/activity_server_files.xml b/src/main/res/layout/activity_server_files.xml index d5b7fe045..e233a987a 100644 --- a/src/main/res/layout/activity_server_files.xml +++ b/src/main/res/layout/activity_server_files.xml @@ -19,7 +19,21 @@ ~ along with Amahi. If not, see . --> - \ No newline at end of file + android:layout_height="match_parent"> + + + + + \ No newline at end of file diff --git a/src/main/res/menu/action_bar_cast_button.xml b/src/main/res/menu/action_bar_cast_button.xml new file mode 100644 index 000000000..c21c11082 --- /dev/null +++ b/src/main/res/menu/action_bar_cast_button.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/action_bar_expanded_controller.xml b/src/main/res/menu/action_bar_expanded_controller.xml new file mode 100644 index 000000000..da19f507e --- /dev/null +++ b/src/main/res/menu/action_bar_expanded_controller.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/action_bar_server_files.xml b/src/main/res/menu/action_bar_server_files.xml index b56c31734..572f75a1d 100644 --- a/src/main/res/menu/action_bar_server_files.xml +++ b/src/main/res/menu/action_bar_server_files.xml @@ -17,7 +17,8 @@ ~ along with Amahi. If not, see . --> - + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 7ecadc075..711b0cd4a 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -40,6 +40,8 @@ Sort Search + cast + No Apps No Files No Shares @@ -75,6 +77,7 @@ Next Play Pause + Play on… Amahi TV diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index bde9b1517..3a614befa 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -67,4 +67,8 @@ + + From 8af90233b950bfae365b1ee9a78969836652118f Mon Sep 17 00:00:00 2001 From: Chirag Maheshwari Date: Thu, 24 Aug 2017 14:56:35 +0530 Subject: [PATCH 2/3] remove custom audio-play image --- .../org/amahi/anywhere/activity/ServerFileAudioActivity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java index 2a0964115..6833e8c86 100644 --- a/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java @@ -663,8 +663,6 @@ private MediaInfo buildMediaInfo() { audioMetadata.putString(MediaMetadata.KEY_TITLE, getFile().getNameOnly()); } - audioMetadata.addImage(new WebImage(Uri.parse("http://alpha.amahi.org/cast/audio-play.jpg"))); - String audioSource = serverClient.getFileUri(getShare(), getFile()).toString(); MediaInfo.Builder builder = new MediaInfo.Builder(audioSource) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) From 389613949eb37d38eed4e3a3f4ec9da4f6676c62 Mon Sep 17 00:00:00 2001 From: Chirag Maheshwari Date: Sun, 27 Aug 2017 11:58:58 +0530 Subject: [PATCH 3/3] adds cast options to image viewer (#269) --- src/main/AndroidManifest.xml | 1 - .../activity/ServerFileImageActivity.java | 109 +++++++++++++++++- .../res/menu/action_bar_server_file_image.xml | 9 ++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 495426571..c89ead2e5 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -18,7 +18,6 @@ --> diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFileImageActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFileImageActivity.java index a3247a67a..fc97d7b31 100644 --- a/src/main/java/org/amahi/anywhere/activity/ServerFileImageActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/ServerFileImageActivity.java @@ -28,6 +28,13 @@ import android.view.Menu; import android.view.MenuItem; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.cast.framework.CastButtonFactory; +import com.google.android.gms.cast.framework.CastContext; +import com.google.android.gms.cast.framework.CastSession; +import com.google.android.gms.cast.framework.SessionManagerListener; +import com.google.android.gms.cast.framework.media.RemoteMediaClient; import com.squareup.otto.Subscribe; import org.amahi.anywhere.AmahiApplication; @@ -55,7 +62,9 @@ * Image activity. Shows images as a slide show. * Backed up by {@link org.amahi.anywhere.view.TouchImageView}. */ -public class ServerFileImageActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener +public class ServerFileImageActivity extends AppCompatActivity implements + ViewPager.OnPageChangeListener, + SessionManagerListener { private static final Set SUPPORTED_FORMATS; @@ -71,6 +80,9 @@ public class ServerFileImageActivity extends AppCompatActivity implements ViewPa @Inject ServerClient serverClient; + private CastSession mCastSession; + private CastContext mCastContext; + private int imagePosition; @Override protected void onCreate(Bundle savedInstanceState) { @@ -83,6 +95,8 @@ protected void onCreate(Bundle savedInstanceState) { setUpImage(); + setUpCast(); + setUpFullScreen(); } @@ -114,6 +128,18 @@ private void setUpImage() { setUpImageListener(); } + private boolean isCastConnected() { + return mCastSession != null && mCastSession.isConnected(); + } + + private void setUpCast() { + mCastContext = CastContext.getSharedInstance(this); + mCastSession = mCastContext.getSessionManager().getCurrentCastSession(); + if (isCastConnected()) { + loadRemoteMedia(); + } + } + private void setUpImageTitle() { setUpImageTitle(getFile()); } @@ -139,7 +165,7 @@ private ServerShare getShare() { } private List getImageFiles() { - List imageFiles = new ArrayList(); + List imageFiles = new ArrayList<>(); for (ServerFile file : getFiles()) { if (SUPPORTED_FORMATS.contains(file.getMime())) { @@ -155,7 +181,8 @@ private List getFiles() { } private void setUpImagePosition() { - getImagePager().setCurrentItem(getImageFiles().indexOf(getFile())); + imagePosition = getImageFiles().indexOf(getFile()); + getImagePager().setCurrentItem(imagePosition); } private void setUpImageListener() { @@ -172,13 +199,18 @@ public void onPageScrollStateChanged(int state) { @Override public void onPageSelected(int position) { + this.imagePosition = position; setUpImageTitle(getImageFiles().get(position)); + if (isCastConnected()) { + loadRemoteMedia(); + } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.action_bar_server_file_image, menu); - + CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, + R.id.media_route_menu_item); return super.onCreateOptionsMenu(menu); } @@ -233,6 +265,7 @@ private void startFileSharingActivity(ServerFile file, Uri fileUri) { protected void onResume() { super.onResume(); + mCastContext.getSessionManager().addSessionManagerListener(this, CastSession.class); BusProvider.getBus().register(this); } @@ -240,10 +273,78 @@ protected void onResume() { protected void onPause() { super.onPause(); + mCastContext.getSessionManager().removeSessionManagerListener(this, CastSession.class); BusProvider.getBus().unregister(this); } public static boolean supports(String mime_type) { return SUPPORTED_FORMATS.contains(mime_type); } + + @Override + public void onSessionEnded(CastSession session, int error) { + onApplicationDisconnected(); + } + + @Override + public void onSessionResumed(CastSession session, boolean wasSuspended) { + onApplicationConnected(session); + } + + @Override + public void onSessionResumeFailed(CastSession session, int error) { + onApplicationDisconnected(); + } + + @Override + public void onSessionStarted(CastSession session, String sessionId) { + onApplicationConnected(session); + } + + @Override + public void onSessionStartFailed(CastSession session, int error) { + onApplicationDisconnected(); + } + + @Override + public void onSessionStarting(CastSession session) {} + + @Override + public void onSessionEnding(CastSession session) {} + + @Override + public void onSessionResuming(CastSession session, String sessionId) {} + + @Override + public void onSessionSuspended(CastSession session, int reason) {} + + private void onApplicationConnected(CastSession castSession) { + mCastSession = castSession; + invalidateOptionsMenu(); + loadRemoteMedia(); + } + + private void onApplicationDisconnected() { + mCastSession = null; + invalidateOptionsMenu(); + } + + private void loadRemoteMedia() { + final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); + if (remoteMediaClient != null) { + remoteMediaClient.load(buildMediaInfo()); + } + } + + private MediaInfo buildMediaInfo() { + ServerFile file = getImageFiles().get(imagePosition); + MediaMetadata imageMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_PHOTO); + imageMetadata.putString(MediaMetadata.KEY_TITLE, file.getNameOnly()); + String imageUrl = serverClient.getFileUri(getShare(), file).toString(); + return new MediaInfo.Builder(imageUrl) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(file.getMime()) + .setMetadata(imageMetadata) + .build(); + } } diff --git a/src/main/res/menu/action_bar_server_file_image.xml b/src/main/res/menu/action_bar_server_file_image.xml index 83c518619..23aa1266d 100644 --- a/src/main/res/menu/action_bar_server_file_image.xml +++ b/src/main/res/menu/action_bar_server_file_image.xml @@ -23,7 +23,16 @@ + + + \ No newline at end of file