B4A Library Casty - Cast Videos to Google Cast Devices (Chromecast)

This is a wrap for this Github project. I started doing the wrap back in 2017 but did not got it working. A few tries later i now got it working ;-)

Casty
Author:
DonManfred
Version: 1.0

  • Class representing local media metadata.
    • Fields:
      • KEY_CONTENT_TYPE As String
      • KEY_IMAGES As String
      • KEY_STUDIO As String
      • KEY_SUBTITLE As String
      • KEY_TITLE As String
      • KEY_URL As String
    • Functions:
      • addImage (url As String)
      • fromBundle (wrapper As android.os.Bundle) As de.donmanfred.MediaItem
      • getImage (index As Int) As String
      • hasImage As Boolean
      • toBundle As android.os.Bundle
    • Properties:
      • ContentType As String
      • Duration As Int
      • Images As java.util.ArrayList [read only]
      • Studio As String
      • SubTitle As String
      • Title As String
      • Url As String
  • CastSession
    Class representing local media metadata.
    • Functions:
      • CastSessionWrapper (session As com.google.android.gms.cast.framework.Session)
      • Initialize (session As com.google.android.gms.cast.framework.Session)
      • IsInitialized As Boolean
    • Properties:
      • Category As String [read only]
      • Connected As Boolean [read only]
      • Connecting As Boolean [read only]
      • Disconnected As Boolean [read only]
      • Disconnecting As Boolean [read only]
      • Resuming As Boolean [read only]
      • SessionId As String [read only]
      • SessionRemainingTimeMs As Long [read only]
      • Suspended As Boolean [read only]
  • Casty
    • Events:
      • onCastSessionUpdated (session As Object, remoteMediaClient As Object)
      • onCastStateChanged (State As Int)
      • onConnected()
      • onDisConnected()
      • onSessionEnded (session As Object, info As Int)
      • onSessionEnding (session As Object)
      • onSessionResumed (session As Object, info As Boolean)
      • onSessionResumeFailed (session As Object, info As Int)
      • onSessionResuming (session As Object, info As String)
      • onSessionStarted (session As Object, info As String)
      • onSessionStartFailed (session As Object, info As Int)
      • onSessionStarting (session As Object)
      • onSessionSuspended (session As Object, info As Int)
    • Functions:
      • addMediaRouteMenuItem (menu As android.view.Menu)
      • addMinicontroller
      • buildMediaInfo (title As String, studio As String, subTitle As String, duration As Int, url As String, mimeType As String, imgUrl As String, bigImageUrl As String) As de.donmanfred.MediaItem
      • configure (receiverId As String)
        Sets the custom receiver ID. Should be used in the {@link Application} class.
        receiverId: the custom receiver ID, e.g. Styled Media Receiver - with custom logo and background
      • configureReceiver (receiverId As String)
      • Initialize (EventName As String)
      • loadMediaAndPlay (mediaData As pl.droidsonroids.casty.MediaData)
      • loadMediaAndPlay3 (mediaInfo As com.google.android.gms.cast.MediaInfo, autoPlay As Boolean, position As Long)
      • loadMediaAndPlayInBackground (mediaData As pl.droidsonroids.casty.MediaData)
      • loadMediaAndPlayInBackground3 (mediaInfo As com.google.android.gms.cast.MediaInfo, autoPlay As Boolean, position As Long)
      • pause
      • play
      • seek (time As Long)
      • setUpMediaRouteButton (menu As android.view.Menu, item As Int)
      • setUpMediaRouteButton2 (MediaButton As android.support.v7.app.MediaRouteButton)
      • togglePlayPause
    • Properties:
      • Buffering [read only]
      • Connected As Boolean [read only]
      • Paused [read only]
      • Player As pl.droidsonroids.casty.CastyPlayer [read only]
      • Playing [read only]
      • UpMediaRouteButton As android.support.v7.app.MediaRouteButton [write only]
  • IntroductoryOverlay
    • Events:
      • onOverlayDismissed()
    • Functions:
      • Initialize (EventName As String, item As android.view.MenuItem)
      • onPause
      • onResume
      • remove
      • setSingleTime
      • show
    • Properties:
      • ButtonText As String [write only]
      • FocusRadius As Float [write only]
      • OverlayColor As Int [write only]
      • TitleText As String [write only]
  • MediaDataBuilder
    • Functions:
      • addPhotoUrl (photoUrl As String) As MediaDataBuilder
      • build As pl.droidsonroids.casty.MediaData
      • Initialize (EventName As String, url As String) As MediaDataBuilder
      • IsInitialized As Boolean
      • setAutoPlay (autoPlay As Boolean) As MediaDataBuilder
      • setContentType (contentType As String) As MediaDataBuilder
      • setMediaType (mediaType As Int) As MediaDataBuilder
      • setPosition (position As Long) As MediaDataBuilder
      • setStreamDuration (streamDuration As Long) As MediaDataBuilder
      • setStreamType (streamType As Int) As MediaDataBuilder
      • setSubtitle (subtitle As String) As MediaDataBuilder
      • setTitle (title As String) As MediaDataBuilder
  • MediaRouteButton
    • Functions:
      • BringToFront
      • DesignerCreateView (base As Panel, lw As Label, props As Map)
      • Initialize (EventName As String, AppID As String)
      • Invalidate
      • Invalidate2 (arg0 As android.graphics.Rect)
      • Invalidate3 (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int)
      • IsInitialized As Boolean
      • RemoveView
      • RequestFocus As Boolean
      • SendToBack
      • SetBackgroundImage (arg0 As android.graphics.Bitmap) As BitmapDrawable
      • SetColorAnimated (arg0 As Int, arg1 As Int, arg2 As Int)
      • SetLayout (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int)
      • SetLayoutAnimated (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int, arg4 As Int)
      • SetVisibleAnimated (arg0 As Int, arg1 As Boolean)
    • Properties:
      • Background As android.graphics.drawable.Drawable
      • Color As Int [write only]
      • DialogFactory As android.support.v7.app.MediaRouteDialogFactory
      • Enabled As Boolean
      • Height As Int
      • Left As Int
      • Padding As Int()
      • Parent As Object [read only]
      • RemoteIndicatorDrawable As android.graphics.drawable.Drawable [write only]
      • RouteSelector As android.support.v7.media.MediaRouteSelector
      • Tag As Object
      • Top As Int
      • Visibility As Int [write only]
      • Visible As Boolean
      • Width As Int

Setup:
- Make your App AppCompat-Compatible. It is mandatory as you need to use the Appcompat Menu creation...
B4X:
Sub Activity_CreateMenu(Menu As ACMenu)
    Menu.Clear
    cast.addMediaRouteMenuItem(Menu)
    Menu.Add(2, 4, "Overflow2", Null)
    Menu.Add(3, 5, "Overflow3", Null)
End Sub
and
B4X:
#If Java
public boolean _onCreateOptionsMenu(android.view.Menu menu) {
    if (processBA.subExists("activity_createmenu")) {
        processBA.raiseEvent2(null, true, "activity_createmenu", false, new de.amberhome.objects.appcompat.ACMenuWrapper(menu));
        return true;
    }
    else
        return false;
}
#End If

- Add the following to your main module
B4X:
#Extends: android.support.v7.app.AppCompatActivity
#AdditionalJar: com.google.android.gms:play-services-cast-framework
#AdditionalJar: casty.aar

Add the following code to your Manifest
B4X:
SetApplicationAttribute(android:theme, "@style/MyAppTheme")

CreateResource(values, theme.xml,
<resources>
    <style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">#0098FF</item>
        <item name="colorPrimaryDark">#007CF5</item>
        <item name="colorAccent">#AAAA00</item>
        <item name="windowNoTitle">true</item>
        <item name="windowActionBar">false</item>
    </style>
</resources>
)
AddApplicationText(
<meta-data android:value="" android:name="app_id"></meta-data>
)
AddApplicationText(
<activity
  android:name="pl.droidsonroids.casty.ExpandedControlsActivity"
  android:theme="@style/MyAppTheme" />
<meta-data
  android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
  android:value="de.donmanfred.CastOptionsProvider" />
<receiver android:name="com.google.android.gms.cast.framework.media.MediaIntentReceiver" />

<service android:name="com.google.android.gms.cast.framework.media.MediaNotificationService" />
<service android:name="com.google.android.gms.cast.framework.ReconnectionService" />
)
CreateResourceFromFile(Macro, FirebaseAnalytics.GooglePlayBase)

Note that the line
<meta-data android:value="" android:name="app_id"></meta-data>
can be used to define YOUR App-ID though it is not needed. If the String is an Empty string then the default Receiverapp will do the Job.

Update Jan 2020:
It is mandatory to use your own app_id which must be registered at the Google Cast Developer console.


Before you begin

There is a one-time, non-refundable $5 registration fee charged for a Google Cast Developer account. Google charge this fee to encourage higher quality applications utilizing Google Cast.



You can see the Example on how to use everything.

In short you can say that you can start Playing Videos when you successfully connected to the Cast device and a Session is opened.

B4X:
Sub Casty_onConnected()
    Log($"Casty_onConnected()"$)
    Dim mb As MediaDataBuilder
    mb.Initialize("","http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4").setAutoPlay(True).setContentType("videos/mp4").setMediaType(1).setStreamType(1).setTitle("DonManfred presents").setSubtitle("B4A Casting Video").addPhotoUrl("https://peach.blender.org/wp-content/uploads/bbb-splash.png?x11217")

    cast.loadMediaAndPlayInBackground(mb.build)
End Sub

chromecast_097.png


 

Attachments

  • CastyV1.0.zip
    60 KB · Views: 455
  • CastyEx.zip
    18.6 KB · Views: 463
  • CastyAddLibs.zip
    65.9 KB · Views: 397
Last edited:

JohnC

Expert
Licensed User
Longtime User
Nice!
 

AnandGupta

Expert
Licensed User
Longtime User
Don Sir Great .. Oh I do not have words, how you work hard to create and then simply provide it to us.
Thanks a lot.

Regards,

Anand
 

AnandGupta

Expert
Licensed User
Longtime User
Looks like you need the device, which seems obvious,
java.lang.RuntimeException: Object should first be initialized (CastSession).

Regards,

Anand
 

jota

Active Member
Licensed User
Longtime User
Hello, thanks for the library. When it came to testing it, I found the following error:

B4X:
java.lang.Exception:  java.lang.RuntimeException: Object should first be initialized (CastSession).
Error occurred on line: 153 (Main)
java.lang.RuntimeException: Object should first be initialized (CastSession).
    at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:50)
    at de.donmanfred.CastSessionWrapper.getSessionId(CastSessionWrapper.java:45)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:732)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:348)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:255)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:144)
    at anywheresoftware.b4a.objects.Timer$TickTack.run(Timer.java:105)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:158)
    at android.app.ActivityThread.main(ActivityThread.java:7224)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
java.lang.Exception:  java.lang.RuntimeException: Object should first be initialized (CastSession).

I've tried it with Chromecast and Miracast on Samsung S5 and Motorola E5.
 

DonManfred

Expert
Licensed User
Longtime User

DonManfred

Expert
Licensed User
Longtime User
Are you running the example? Or did you implemented it in your app?

I just tried my exampleproject. Got a few missing dependencies (maybe due to updated artifacts).
B4X:
#AdditionalJar: dagger-android-2.23.2.aar
#AdditionalJar: dagger-2.23.2
#AdditionalJar: javax.inject-1

Added them and then the example works as expected.

B4X:
Sub Activity_CreateMenu(Menu As ACMenu)
    Menu.Clear
    cast.addMediaRouteMenuItem(Menu)
    Menu.Add(2, 4, "Overflow2", Null)
    Menu.Add(3, 5, "Overflow3", Null)
End Sub
It is adding a MediaRouteMenuItem to the Menu (which can be accessed with appcompat).

WhatsApp Image 2019-12-25 at 14.57.01.jpeg

When you click the MediaRouteMenuItem a castingwindow appear where you can start a session.


One of the events raised is

B4X:
Sub Casty_onSessionStarted(session As Object, info As String)
    Log($"Casty_onSessionStarted(${session}, ${info})"$)
    Dim sess As CastSession = session
    Log($"Session: ID ${sess.SessionId} ${sess.Category}"$)
    ActiveSession = sess
End Sub

*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **
Casty_onCastStateChanged(Not Connected)
** Activity (main) Pause, UserClosed = false **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **
Casty_onCastStateChanged(Connecting to Castdevice)
Casty_onSessionStarting(com.google.android.gms.cast.framework.CastSession@7c40b47)
Session: ID null com.google.android.gms.cast.CATEGORY_CAST/4F8B3483///ALLOW_IPV6
Casty_onCastStateChanged(Connected)
Casty_onSessionStarted(com.google.android.gms.cast.framework.CastSession@7c40b47, a23ab117-9f57-4e27-9b42-a80ccf02d89d)
Session: ID a23ab117-9f57-4e27-9b42-a80ccf02d89d com.google.android.gms.cast.CATEGORY_CAST/4F8B3483///ALLOW_IPV6
Casty_onConnected()
Casty_onCastSessionUpdated()
MediaClient_onProgressUpdated( 0:00:00 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:00 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:01 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:01 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:01 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:01 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:02 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:02 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:02 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:02 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:03 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:03 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:03 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:03 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:04 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:04 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:04 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:04 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:05 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:05 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:05 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:05 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:06 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:06 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:06 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:06 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:07 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:07 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:07 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:08 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:08 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:08 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:08 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:09 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:09 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:09 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:09 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:10 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:10 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:10 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:10 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:11 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:11 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:11 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:11 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:12 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:12 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:12 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:12 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:13 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:13 - 0:10:34)
MediaClient_onProgressUpdated( 0:00:13 - 0:10:34)
Casty_onCastStateChanged(Not Connected)
Casty_onSessionEnding(com.google.android.gms.cast.framework.CastSession@7c40b47)
Session: ID null com.google.android.gms.cast.CATEGORY_CAST/4F8B3483///ALLOW_IPV6
Casty_onSessionEnded()
Session: ID null com.google.android.gms.cast.CATEGORY_CAST/4F8B3483///ALLOW_IPV6
Casty_onDisConnected()

I do not se a timer in my example though...
 

DonManfred

Expert
Licensed User
Longtime User
Some more help and advice would be very much appreciated!
i only can tell that i did my tests using V9.5 for sure. I always use the latest Version.

It can be that you need to use an older SDK using V8.3 but i´m not sure. You better start a new thread in the Questionsforum about this.

Please also test with "26".
I did use targetsdk 26 in my Tests.
gives a "Casty not initialized" error is probably a clue
You did setup a Cast App in the CAST Console?

You did adapt the Manifest entry to match your appid?

B4X:
AddApplicationText(
<meta-data android:value="<your app id>" android:name="app_id"></meta-data> 
)
AddApplicationText(
<activity
  android:name="pl.droidsonroids.casty.ExpandedControlsActivity"
  android:theme="@style/MyAppTheme" />
<meta-data
  android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
  android:value="de.donmanfred.CastOptionsProvider" />
<receiver android:name="com.google.android.gms.cast.framework.media.MediaIntentReceiver" />

<service android:name="com.google.android.gms.cast.framework.media.MediaNotificationService" />
<service android:name="com.google.android.gms.cast.framework.ReconnectionService" />
)
 

DonManfred

Expert
Licensed User
Longtime User
To add:


There is a one-time, non-refundable $5 registration fee charged for a Google Cast Developer account. We charge this fee to encourage higher quality applications utilizing Google Cast.
 

DonManfred

Expert
Licensed User
Longtime User
I have the feeling that so far I am the only one who is trying to use Casty
could be
I am also hoping that once I get Casty working that it will be possible to play radio streams (and maybe also TV streams) via the Chomecast, i.e. to make a complete Chromecast Media Center.
I don´t know whether it works playing ANY online stream. The example is using the MediaDataBuilder to setup an Online video and Snapshot
B4X:
    Dim mb As MediaDataBuilder
    mb.Initialize("","http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4").setAutoPlay(True).setContentType("videos(mp4").setMediaType(1).setStreamType(1).setTitle("DonManfred presents").setSubtitle("B4A Casting Video").addPhotoUrl("https://peach.blender.org/wp-content/uploads/bbb-splash.png?x11217")

I don´t know it that works with Radiostreams. You probably need to find out yourself
 
Top