react-native-google-cast/react-native-google-cast

useRemoteMediaClient return null even when session is created.

rdhox opened this issue · 9 comments

rdhox commented

Describe the bug
Bug with the last update: useRemoteClient return null client even after session successfully start.
To get around this problem, I had to take the client from the session object return by the event onSessionStarted of the object sessionManager and passed it as an argument to my function that load media.

To Reproduce
Normal use from the doc.

Expected behavior
The hook useRemoteClient should always return a fresh valid client object if session is on.

What I tried
Using React useCallback hook for my diffuseQueue function with the client object as dependence to be sure to have the "fresh" one in my function, but didn't work.

Device:

  • iOS (iPhone XS)
  • android (A5)

Here my code:

...

  async function diffuseQueue(queue: MediaQueueItem[], client: RemoteMediaClient): Promise<boolean> {
    try {
      console.log('CLIENT: ', client)
      if (client) {
        await client.loadMedia({
          queueData: {
            items: queue,
          },
          autoplay: true
        });
        return true;
      } else {
        console.log("No client to load media.")
        return false;
      }
    } catch (error) {
      console.log('ERROR DIFFUSE: ', error);
      return false;
    }
  };

  async function startCasting(client: RemoteMediaClient) {
    try {
      setLoading(true);
      const currentQueue = await TrackPlayer.getQueue();
      const playlistServer = await fetchPlaylistFromServer(currentQueue.map(t => t._id));
      if (typeof playlistServer !== 'undefined') {
        const currentIndex = await TrackPlayer.getCurrentTrack();
        const cloneQueue = JSON.parse(JSON.stringify(currentQueue));
        cloneQueue.splice(0, currentIndex);
        const trackToCast = [...cloneQueue, ...playlistServer];
        const formatPlaylist = trackToCast.map(t => transformTrackToMediaQueueItem(t));
        const loadMediaOK = await diffuseQueue(formatPlaylist, client);
        if (loadMediaOK) {
          setCurrentMedia(formatPlaylist[0]);
          await TrackPlayer.setVolume(0.5);
          await TrackPlayer.pause();
        } else {
          stopCasting();
          showMessage({
            message: "Error while creating  chromecast's session.",
            type: "danger",
          });
        }
      } else {
        stopCasting();
      }
    } catch (error) {
      console.log("ERROR START CASTING: ", error);
      stopCasting();
    }
    setLoading(false);
  }

  const client = useRemoteMediaClient();
  const sessionManager = ChromeCast.getSessionManager();

  // Handle Session
  useEffect(() => {

    const starting = sessionManager.onSessionStarting(async (session) => {
      console.log('STARTING')
    });

    const started = sessionManager.onSessionStarted(async (session) => {
      try {
        console.log("SESSION: ", session)
        const { client } = session;
        // BUG with hook useRemoteClient that seems to not give the created client, so we pass this one to the function startCasting.

        startCasting(client);
      } catch (error) {
        console.log('ERROR STARTED: ', error);
      }
    });

    const ended = sessionManager.onSessionEnded(session => {
      console.log('END SESSION');
      stopCasting();
    });

    return () => {
      started.remove();
      ended.remove();
      starting.remove();
    };
  }, [sessionManager]);

...

Hi! Can you post the code you had before that wasn't working? What you're doing now with sessionManager is pretty much what the useRemoteMediaClient/useSession hook does so I'm wondering why it wouldn't work.

rdhox commented

Here the code that doesn't work:

...

  async function diffuseQueue(queue: MediaQueueItem[]): Promise<boolean> {
    try {
      if (client) {
        await client.loadMedia({
          queueData: {
            items: queue,
          },
          autoplay: true
        });
        return true;
      } else {
        console.log("No client to load media.")
        return false;
      }
    } catch (error) {
      console.log('ERROR DIFFUSE: ', error);
      return false;
    }
  };

  async function startCasting() {
    try {
      setLoading(true);
      const currentQueue = await TrackPlayer.getQueue();
      const playlistServer = await fetchPlaylistFromServer(currentQueue.map(t => t._id));
      if (typeof playlistServer !== 'undefined') {
        const currentIndex = await TrackPlayer.getCurrentTrack();
        const cloneQueue = JSON.parse(JSON.stringify(currentQueue));
        cloneQueue.splice(0, currentIndex);
        const trackToCast = [...cloneQueue, ...playlistServer];
        const formatPlaylist = trackToCast.map(t => transformTrackToMediaQueueItem(t));
        const loadMediaOK = await diffuseQueue(formatPlaylist);
        if (loadMediaOK) {
          setCurrentMedia(formatPlaylist[0]);
          await TrackPlayer.setVolume(0.5);
          await TrackPlayer.pause();
        } else {
          stopCasting();
          showMessage({
            message: "Error while creating  chromecast's session.",
            type: "danger",
          });
        }
      } else {
        stopCasting();
      }
    } catch (error) {
      console.log("ERROR START CASTING: ", error);
      stopCasting();
    }
    setLoading(false);
  }

  const client = useRemoteMediaClient();
  const sessionManager = ChromeCast.getSessionManager();

  // Handle Session
  useEffect(() => {

    const starting = sessionManager.onSessionStarting(async (session) => {
      console.log('STARTING')
    });

    const started = sessionManager.onSessionStarted(async (session) => {
      try {
        console.log("SESSION: ", session)
        startCasting();
      } catch (error) {
        console.log('ERROR STARTED: ', error);
      }
    });

    const ended = sessionManager.onSessionEnded(session => {
      console.log('END SESSION');
      stopCasting();
    });

    return () => {
      started.remove();
      ended.remove();
      starting.remove();
    };
  }, [sessionManager]);

...

Almost the same, I was just expecting the diffuseQueue function to get the client object return by the useRemoteMediaClient hook, but I was having null each time.

But this code was working for a long time before it stopped.

I think the problem you're having is because the useEffect retains an old version of the startCasting function that was bound to the client on the initial render and never updated because the sessionManager dependency never changes. Or something similar with the way you manage the hook. I recommend you set up eslint, it will warn you that the dependency list of the useEffect is incomplete.

If you want to start casting whenever the session/client is established, it would be easier to do so like this:

useEffect(() => {
  if (!client) return
  // ...start casting
}, [client])
rdhox commented

Thank I will check that!

rdhox commented

I tried to group my logic on the useEffect to start the cast like you said but it didn't work, same error than before.

if you're still calling another function from the effect you need to wrap it in useCallback and pass as a dependency

rdhox commented

I put everything in the useEffect to be sure to have no exterior dependencies.

can you post the code and what error you're getting

rdhox commented
...
  // we start casting if client is not null
  useEffect(() => {
    async function diffuseQueue(queue: MediaQueueItem[]): Promise<boolean> {
      try {
        console.log('CLIENT: ', client)
        if (client) {
          await client.loadMedia({
            queueData: {
              items: queue,
            },
            autoplay: true
          });
          return true;
        } else {
          console.log("No client to load media.")
          return false;
        }
      } catch (error) {
        console.log('ERROR DIFFUSE: ', error);
        return false;
      }
    };

    async function startCasting() {
      try {
        setLoading(true);
        const currentQueue = await TrackPlayer.getQueue();
        const playlistServer = await fetchPlaylistFromServer(currentQueue.map(t => t._id));
        if (typeof playlistServer !== 'undefined') {
          const currentIndex = await TrackPlayer.getCurrentTrack();
          const cloneQueue = JSON.parse(JSON.stringify(currentQueue));
          cloneQueue.splice(0, currentIndex);
          const trackToCast = [...cloneQueue, ...playlistServer];
          const formatPlaylist = trackToCast.map(t => transformTrackToMediaQueueItem(t));
          const loadMediaOK = await diffuseQueue(formatPlaylist);
          if (loadMediaOK) {
            setCurrentMedia(formatPlaylist[0]);
            await TrackPlayer.setVolume(0.5);
            await TrackPlayer.pause();
          } else {
            stopCasting();
            showMessage({
              message: "Error while creating a chromecast's session.",
              type: "danger",
            });
          }
        } else {
          stopCasting();
        }
      } catch (error) {
        console.log("ERROR START CASTING: ", error);
        stopCasting();
      }
      setLoading(false);
    }

    if (!client) return;
    startCasting();
  }, [client, TrackPlayer]);
...

client is null in the diffuseQueue function.