I'm using the Companion library for casting video from my app to the Chromecast.
Is there any way ho I can add the subtitles / closed captions toggle button so the user will be able to turn them on and off?
I'm reading their documentation where I can see, how is possible to set the subtitle URL
MediaTrack englishSubtitle = new MediaTrack.Builder(1 /* ID */,
MediaTrack.TYPE_TEXT)
.setName("English Subtitle")
.setSubtype(MediaTrack.SUBTYPE_SUBTITLE)
.setContentId("https://some-url/caption_en.vtt")
/* language is required for subtitle type but optional otherwise */
.setLanguage("en-US")
.build();
But no words about where I should handle the show / hide actions.
Do you have any suggestions how I can add the toggle button and handle show / hide actions?
I'm using the VideoCastManager which is using the VideoCastControllerActivity from the casting library.
This is my CastConfiguration
// Build a CastConfiguration object and initialize VideoCastManager
CastConfiguration options = new CastConfiguration.Builder(MyAppConfig.CHROMECAST_APP_ID)
.enableAutoReconnect()
.enableCaptionManagement()
.enableDebug()
.enableLockScreen()
.enableNotification()
.enableWifiReconnection()
.setCastControllerImmersive(true)
.setLaunchOptions(false, Locale.getDefault())
.setNextPrevVisibilityPolicy(CastConfiguration.NEXT_PREV_VISIBILITY_POLICY_DISABLED)
.addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_REWIND, false)
.addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE, true)
.addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_DISCONNECT, true)
.setForwardStep(10)
.build();
// Google Chrome Cast initialization of the VideoCastManager that is a helper class from the CasCompanionLibrary
// that helps us deal with the flow of communicating with chromecast
VideoCastManager.
initialize(this, options)
.setVolumeStep(MyAppConfig.VOLUME_INCREMENT);
And there I'm creating the MediaInfo
MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
mediaMetadata.addImage(new WebImage(Uri.parse(MyAppConfigBase.IMAGE_API_ENDPOINT + movieVideoItem.getImages().getKeyart())));
mediaMetadata.addImage(new WebImage(Uri.parse(MyAppConfigBase.IMAGE_API_ENDPOINT + movieVideoItem.getImages().getKeyart())));
mediaMetadata.putString(MediaMetadata.KEY_TITLE, movieVideoItem.getTitle());
mediaMetadata.putString(MediaMetadata.KEY_SUBTITLE, movieVideoItem.getDescription());
mediaMetadata.putString("movie-urls", url);
mediaMetadata.putString("content-type", movieVideoItem.getContent().getHighRes().getType());
MediaTrack englishSubtitle = new MediaTrack.Builder(1 /* ID */, MediaTrack.TYPE_TEXT)
.setName("English Subtitle")
.setSubtype(MediaTrack.SUBTYPE_CAPTIONS)
.setContentId(closedCaptionsUrl)
/* language is required for subtitle type but optional otherwise */
.setLanguage("en-US")
.build();
List tracks = new ArrayList();
tracks.add(englishSubtitle);
MediaInfo mediaInfo = new MediaInfo.Builder(url)
.setStreamDuration(movieVideoItem.getDuration())
.setStreamType(MediaInfo.STREAM_TYPE_NONE)
.setContentType(type)
.setMetadata(mediaMetadata)
.setMediaTracks(tracks)
.setCustomData(customData)
.build();
You need to do the following:
Make sure your MediaInfo items have tracks info.
Make sure that tracks are enabled in the settings and enable the support for tracks when you configure CastVideoManager.
Register a OnTracksSelectedListener listener in your, say, activity so that when tracks change, your activity can be notified.
4.Add a button to your activity and upon a click on the button, call a method like the following.
private void showTracksChooserDialog()
throws TransientNetworkDisconnectionException, NoConnectionException {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
Fragment prev = getSupportFragmentManager().findFragmentByTag(DIALOG_TAG);
if (prev != null) {
transaction.remove(prev);
}
transaction.addToBackStack(null);
// Create and show the dialog.
TracksChooserDialog dialogFragment = TracksChooserDialog
.newInstance(mCastManager.getRemoteMediaInformation());
dialogFragment.show(transaction, DIALOG_TAG);
}
This will open a (fragment) dialog that shows the current text and audio tracks and allows the user to select one. When one is selected and Ok is pressed in that dialog, the listener that you had registered in the previous step is called and then you can enable the track in your listener.
Make sure you remove the listener when you leave your activity.
Related
I need to check if the Android phone my app runs on is using casting which is enabled outside of my app.
It seems CastSession or SessionManager can provide the session related to my app which is not helpful for me.
For example, I can start casting with an app called xx which will cast or mirror the entire screen of my phone. Now, I need to notify when I open my app that the phone's screen is casting/mirroring so I can prevent showing specific content on my app.
I checked it with the code below:
val isCastingEnabledLiveData = MutableLiveData<Boolean>()
fun isCastingEnabled(context: Context): Boolean {
val mediaRouter = MediaRouter.getInstance(context)
if (mediaRouter.routes.size <= 1) {
isCastingEnabledLiveData.value = false
return
}
val selector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
mediaRouter.addCallback(selector, object : MediaRouter.Callback() {
override fun onRouteChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
super.onRouteChanged(router, route)
isCastingEnabledLiveData.value = if (route != mediaRouter.defaultRoute) {
route?.connectionState != MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED
} else false
}
})
}
you can check whether the phone screen is casting or not by using the MediaRouter class.
Here is an example of how you could check if the phone screen is casting:
MediaRouter mediaRouter = (MediaRouter)
getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
if(route.isDefault()){
// Screen is not casting
} else {
// Screen is casting
}
This code uses the getSelectedRoute() method of the MediaRouter class to get the currently selected route. If the returned RouteInfo object is the default route, then the screen is not casting, otherwise it is.
Please note that this code uses the getSystemService(Context.MEDIA_ROUTER_SERVICE) method to get an instance of the MediaRouter class, so it should be called from an Activity or Service context.
Additionally, you could also use MediaRouter.Callback and MediaRouter.addCallback to set a callback to monitor the state of the casting, so you could get the updates on the casting state change as well.
I want to show a system dialog to user to select from available applications for sharing text from my app. I can do this by using createChooser function from Intent class. But i also want to listen for the system dialog result, so that i can disable/enable my share button to prevent creating multiple system dialogs overlapping each other.
To do this i need to know whenever the dialog is dismissed or an app option is selected by the user. So i need the result of the chooser Dialog i have created.
I was able to get the selected app, but was not able to listen the dismiss event for the system dialog because Intent.ACTION_CLOSE_SYSTEM_DIALOGS event is deprecated for third party applications. So is there any other way on how to know when the system dialog is closed?
Thanks in advance.
I was able to listen the result using rememberLauncherForActivityResult Composable function by combining it with ActivityResultContracts.StartActivityForResult abstract class. you can see the usage example i have implemented below. Please share your opinions/corrections or alternatives for my problem.
var shareEnabled by remember { mutableStateOf(true) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
// you can use the ActivityResult(it) here
shareEnabled = true
}
Button(
onClick = {
shareEnabled = false
launcher.launch(getShareText().shareExternal())
},
enabled = shareEnabled
)
shareExternal is an extension function that creates and returns the chooser Intent;
fun String.shareExternal(): Intent {
val dataToShare = this
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, dataToShare)
type = "text/plain"
}
return Intent.createChooser(sendIntent, null)
}
How to fully integrate Google Cast v3 with ExoPlayer v2? The activity will contain a FrameLayout with a com.google.android.exoplayer2.ui.SimpleExoPlayerView in it. The Google tutorial only covers integration with VideoView.
The code below is available in a Kotlin class in this Gist that should help people trying to set up their CastPlayer for the first time:
https://gist.github.com/stefan-zh/fd52e0ee06088ac4086d2ea3fb7d7f3e
Also, going through this tutorial from Google will help you: https://codelabs.developers.google.com/codelabs/cast-videos-android/index.html#0
I also used this tutorial to get started: https://android.jlelse.eu/sending-media-to-chromecast-has-never-been-easier-c331eeef1e0a
Here is a breakdown how to achieve this using ExoPlayer and its Cast extension.
1. You will need these dependencies:
// ExoPlayer is an advanced media player for playing media files
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation "com.google.android.exoplayer:extension-cast:$exoplayer_version"
2. You will need the Cast button
The Cast button can be added in the options menu for the activities. This is the recommended way to do it.
Add the following to res/menu/browse.xml (in my case the menu file is called browse.xml):
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item
android:id="#+id/media_route_menu_item"
android:title="#string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Then add the following code to your Activity to enable the castButton:
/**
* We need to populate the Cast button across all activities as suggested by Google Cast Guide:
* https://developers.google.com/cast/docs/design_checklist/cast-button#sender-cast-icon-available
*/
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val result = super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
castButton = CastButtonFactory.setUpMediaRouteButton(applicationContext, menu, R.id.media_route_menu_item)
return result
}
3. Declare your Options Provider for the Cast context
You need this so that you get the options dialog with the list of devices that you can cast to. Add this to your AndroidManifest.xml in the application tag:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider" />
4. Your Activity needs to implement ExoPlayer's Cast Extension interface SessionAvailabilityListener
This interface will allow you to listen for changes in the Cast session availability. Based on whether a Cast session is available you can direct playback to the local player or the remote player.
override fun onCastSessionAvailable() {
playOnPlayer(castPlayer)
}
override fun onCastSessionUnavailable() {
playOnPlayer(exoPlayer)
}
5. You will need logic to initialize the players:
Notice how we are calling castPlayer?.setSessionAvailabilityListener(this) where this refers to your Activity that implements the SessionAvailabilityListener interface. The listener's methods will be called when the Cast session availability changes.
private fun initializePlayers() {
exoPlayer = SimpleExoPlayer.Builder(this).build()
playerView.player = exoPlayer
if (castPlayer == null) {
castPlayer = CastPlayer(castContext)
castPlayer?.setSessionAvailabilityListener(this)
}
// start the playback
if (castPlayer?.isCastSessionAvailable == true) {
playOnPlayer(castPlayer)
} else {
playOnPlayer(exoPlayer)
}
}
6. You need logic to play on the selected player:
This method allows you to store the playback state (playbackPosition, playWhenReady, or windowIndex)
Create the correct media type for the local or remote players
Select which player should start playback
playOnPlayer() method:
private fun playOnPlayer(player: Player?) {
if (currentPlayer == player) {
return
}
// save state from the existing player
currentPlayer?.let {
if (it.playbackState != Player.STATE_ENDED) {
it.rememberState()
}
it.stop(true)
}
// set the new player
currentPlayer = player
// set up the playback
// if the current player is the ExoPlayer, play from it
if (currentPlayer == exoPlayer) {
// build the MediaSource from the URI
val uri = Uri.parse(videoClipUrl)
val dataSourceFactory = DefaultDataSourceFactory(this#SampleCastingPlayerActivity, "exoplayer-agent")
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
// use stored state (if any) to resume (or start) playback
exoPlayer?.playWhenReady = playWhenReady
exoPlayer?.seekTo(currentWindow, playbackPosition)
exoPlayer?.prepare(mediaSource, false, false)
}
// if the current player is the CastPlayer, play from it
if (currentPlayer == castPlayer) {
val metadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
metadata.putString(MediaMetadata.KEY_TITLE, "Title")
metadata.putString(MediaMetadata.KEY_SUBTITLE, "Subtitle")
metadata.addImage(WebImage(Uri.parse("any-image-url")))
val mediaInfo = MediaInfo.Builder(videoClipUrl)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(MimeTypes.VIDEO_MP4)
.setMetadata(metadata)
.build()
val mediaItem = MediaQueueItem.Builder(mediaInfo).build()
castPlayer?.loadItem(mediaItem, playbackPosition)
}
}
7. Remember state and clean up resources
Each time you switch your application between background or foreground you would need to release or request resources. Each time you release the Player's resources back to the system you would need to save its state.
/**
* Remembers the state of the playback of this Player.
*/
private fun Player.rememberState() {
this#SampleCastingPlayerActivity.playWhenReady = playWhenReady
this#SampleCastingPlayerActivity.playbackPosition = currentPosition
this#SampleCastingPlayerActivity.currentWindow = currentWindowIndex
}
/**
* Releases the resources of the local player back to the system.
*/
private fun releaseLocalPlayer() {
exoPlayer?.release()
exoPlayer = null
playerView.player = null
}
/**
* Releases the resources of the remote player back to the system.
*/
private fun releaseRemotePlayer() {
castPlayer?.setSessionAvailabilityListener(null)
castPlayer?.release()
castPlayer = null
}
Google Cast SDK is independent of Local Player, you can use ExoPlayer or MediaPlayer ( VideoView )
Once your APP has an active session, place the url in MediaInfo
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
movieMetadata.putString(MediaMetadata.KEY_TITLE, "Title")
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, "Sub")
val mediaLoadOptions = MediaInfo.Builder( < URL > )
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(< Content Type of Media>)
.setMetadata(movieMetadata)
.setStreamDuration(<Media Duration >)
.build()
mCastSession.remoteMediaClient.load(buildMediaInfo(url), mediaLoadOptions)
If you need to stream a local media, you will need to stream it for yourself, using NanoHttpd or another of your choice, and also implement a Cast Receiver
I am using CastCompanionLibrary-android in my app and following CastVideos-android to play live streams on chromecast. Now the streaming of video works fine on my local player but when it comes to cast that video, it wont play. Rather it just show me my registered receiver app name and on sender app the VideoCastControllerActivity opens with only a loader which wont end.
I have registered both my receiver app (Styled Media Receiver) and the device on Google chrome cast console. Also, I tried to debug the receiver app, it show nothing on the debugger or in console.
Here is the code snippet of my sender app:
private void initMediaRouter() {
BaseCastManager.checkGooglePlayServices(this);
mCastManager = VideoCastManager.getInstance();
MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); //also tried MediaMetadata.MEDIA_TYPE_GENERIC
mediaMetadata.putString(MediaMetadata.KEY_TITLE, channel.getChannelName());
mediaMetadata.putString(MediaMetadata.KEY_SUBTITLE, channel.getCatName());
info = new MediaInfo.Builder(channel.getHttpStream())
.setMetadata(mediaMetadata)
.setContentType("Video/*")
.setStreamType(MediaInfo.STREAM_TYPE_LIVE)
.build();
castConsumer = new CastConsumer();
mCastManager.addVideoCastConsumer(castConsumer);
}
this function is called on the player activity's OnCreate() function.
The Listener (I'm getting true value for wasLaunched param)
private class CastConsumer extends VideoCastConsumerImpl {
#Override
public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
mCastManager.startVideoCastControllerActivity(PlayerActivity.this, info, 0, true);
}
}
And in the onCreate() function of Application:
String applicationId = getString(R.string.cast_app_id);
CastConfiguration options = new CastConfiguration.Builder(applicationId)
.enableAutoReconnect()
.enableCaptionManagement()
.enableDebug()
.enableLockScreen()
.enableNotification()
.enableWifiReconnection()
.setCastControllerImmersive(true)
.setLaunchOptions(false, Locale.getDefault())
.addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE, true)
.addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_DISCONNECT, true)
.build();
VideoCastManager.initialize(this, options);
can you please tell me where i am going wrong? Thanks.
The content type might be an issue. Try properly setting it to something like video/mp4 if that's the case.
I am developing a mobile android app and using Android Pay/Google Wallet to retrieve CC information from the user. I was able to successfully get the GitHub sample application working.
Please see the image below:
It appears that the screen shown here uses a dynamic masked wallet fragment. The payment method and shipping address and change buttons are automatically generated by Android APIs.
How can I customize my own UI for this fragment?
How can I listen to the onClick event of the "CHANGE" button?
How can I use the "Android Pay" logo in green (in the image)? The sample app appears to still use the built in "Google Wallet" logo.
I found out how to use the Android Pay logo in the MaskedWalletFragment screen. Simply use the following API:
https://developers.google.com/android/reference/com/google/android/gms/wallet/fragment/WalletFragmentStyle.LogoImageType.html#ANDROID_PAY
The key is to call the following:
setMaskedWalletDetailsLogoImageType(int)
Use constant value of 3 for Android Pay. The rest are deprecated.
U have use the methods
.setBuyButtonAppearance(WALLET_APPEARANCE) in CkecoutActivity
.setMaskedWalletDetailsLogoImageType(mWALLET_APPEARANCE) in ConfirmationActivity
.useGoogleWallet() in FullWalletButton
Android wallet is deprecated, in my case i did this:
check id the device have NFC support (android pay use NFC)
if have nfc support use android pay, if dont have i use android wallet.
First in the Constants.java add:
//values to change buyapparence (android pay-wallet)
public static final int GOOGLE_WALLET_CLASSIC=1;
public static final int GOOGLE_WALLET_GRAYSCALE =2;
public static final int GOOGLE_WALLET_MONOCHROME=3;
public static final int ANDROID_PAY_DARK = 4;
public static final int ANDROID_PAY_LIGHT=5;
public static final int ANDROID_PAY_LIGHT_WITH_BORDER=6;
Then in utils add the nfcController.java with this method:
public boolean NFCsupport(){
boolean nfcSupport;
NfcManager manager = (NfcManager)mAppContext.getSystemService(Context.NFC_SERVICE);
NfcAdapter adapter = manager.getDefaultAdapter();
if (adapter != null && adapter.isEnabled()) {
nfcSupport=true;
//Yes NFC available
}else{
nfcSupport=false;
//Your device doesn't support NFC
}
return nfcSupport;
}
Then in your CheckoutActivity.java or when you have wallet implemention add this:
if(nfcController.NFCsupport()){
//turn on nfc (other method in util nfcController.java)
nfcController.enableNfcPower(true);
//show nfc payment(android pay)
mWALLET_APPEARANCE=Constants.ANDROID_PAY_LIGHT;
createAndAddWalletFragment(mWALLET_APPEARANCE);
Log.d("nfc", "you have nfc support");
}else{
Log.d("nfc", "dont have nfc support");
//show not nfc payment(wallet)
mWALLET_APPEARANCE=Constants.GOOGLE_WALLET_CLASSIC;
createAndAddWalletFragment(mWALLET_APPEARANCE);
}
In your createAndAddWalletFragment(int WALLET_APPEARANCE) change the appearance flags:
WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle()
.setBuyButtonText(BuyButtonText.BUY_WITH_GOOGLE)
.setBuyButtonAppearance(WALLET_APPEARANCE)
.setBuyButtonWidth(WalletFragmentStyle.Dimension.MATCH_PARENT);
Second, sent the wallet_appareance in the intent:
intent.putExtra("wallet_appearance",mWALLET_APPEARANCE);
and in your confirmationActivity update this funcion to listen the event with the logo correctly in the createAndAddWalletFragment():
WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle()
.setMaskedWalletDetailsLogoImageType(mWALLET_APPEARANCE)//from getintent
.setMaskedWalletDetailsTextAppearance(
R.style.BikestoreWalletFragmentDetailsTextAppearance)
.setMaskedWalletDetailsHeaderTextAppearance(
R.style.BikestoreWalletFragmentDetailsHeaderTextAppearance)
.setMaskedWalletDetailsBackgroundColor(
getResources().getColor(R.color.white))
.setMaskedWalletDetailsButtonBackgroundResource(
R.drawable.bikestore_btn_default_holo_light);
Finally in your fullWalletconfirmationbutton.java in the onCreate method dont forget the function useGoogleWallet to create and setup the fragment:
// Set up an API client;
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.setAccountName(accountName)
.addApi(Wallet.API, new Wallet.WalletOptions.Builder()
.useGoogleWallet()
.setEnvironment(Constants.WALLET_ENVIRONMENT)
.setTheme(WalletConstants.THEME_HOLO_LIGHT)
.build())
.build();
and you have android pay and android wallet support.