Chromcast Working Example - SergeiGolos/wod-wiki GitHub Wiki
Chromecast Working Example
Client
'use client'importReact,{useEffect,useState,useRef}from'react'import{usePathname}from'next/navigation'constCastControls: React.FC=()=>{const[castAvailable,setCastAvailable]=useState(false)constcastButtonRef=useRef<HTMLButtonElement>(null)constcastSessionRef=useRef<chrome.cast.Session|null>(null)constpathname=usePathname()// Extract room code from pathname if we're on a host pageconstroomCode=pathname?.startsWith('/host/') ? pathname.split('/').pop() : nulluseEffect(()=>{constinitializeCastApi=()=>{if(!window.chrome||!window.chrome.cast){console.log('Cast SDK not available yet.')return}console.log('Cast SDK available.')constsessionRequest=newchrome.cast.SessionRequest(process.env.NEXT_PUBLIC_CAST_APPLICATION_ID||'')constapiConfig=newchrome.cast.ApiConfig(sessionRequest,(session)=>{// Session listenerconsole.log('New session',session)castSessionRef.current=session// If we have a room code, send it to the receiverif(roomCode){constmessage={ roomCode }session.sendMessage('urn:x-cast:com.google.cast.cac',message,()=>console.log('Message sent successfully'),(error)=>console.error('Failed to send message:',error))}},(availability)=>{// receiver listenerconsole.log('Receiver availability:',availability)if(availability===chrome.cast.ReceiverAvailability.AVAILABLE){setCastAvailable(true)}else{setCastAvailable(false)}})chrome.cast.initialize(apiConfig,()=>{console.log('Cast API initialized successfully.')// Initialize the Cast button after API is readyif(window.google&&window.google.cast&&window.google.cast.framework){constcastContext=window.google.cast.framework.CastContext.getInstance();castContext.setOptions({receiverApplicationId: process.env.NEXT_PUBLIC_CAST_APPLICATION_ID||''});}},(error)=>console.error('Cast API initialization failed:',error))}// Set up the callback function for when the Cast API is availablewindow.__onGCastApiAvailable=(available,reason)=>{console.log('Cast API available:',available,reason||'')if(available){initializeCastApi()}else{console.error('Google Cast SDK could not be loaded.')}}// If the Cast API is already available, initialize it directlyif(window.chrome&&window.chrome.cast&&window.chrome.cast.isAvailable){initializeCastApi()}return()=>{// Cleanupwindow.__onGCastApiAvailable=undefined;}},[roomCode])// Add roomCode to dependenciesconsthandleCastClick=()=>{if(!window.chrome||!window.chrome.cast)return;chrome.cast.requestSession((session)=>{console.log('Session started',session)castSessionRef.current=session// Send room code to receiver when session startsif(roomCode){constmessage={ roomCode }session.sendMessage('urn:x-cast:com.google.cast.cac',message,()=>console.log('Room code sent to receiver'),(error)=>console.error('Failed to send room code:',error))}},(error)=>{console.error('Error starting cast session:',error)})}// Only show cast button on host pagesif(!roomCode)returnnull;return(<div>{castAvailable ? (<buttonref={castButtonRef}onClick={handleCastClick}className="cast-button"style={{width: '32px',height: '32px',border: 'none',background: 'url("https://www.gstatic.com/images/icons/material/system/2x/cast_black_24dp.png") center/cover no-repeat',cursor: 'pointer'}}aria-label="Cast to device"/>) : (<buttondisabledstyle={{width: '32px',height: '32px',border: 'none',background: 'url("https://www.gstatic.com/images/icons/material/system/2x/cast_disabled_black_24dp.png") center/cover no-repeat',opacity: 0.5,cursor: 'not-allowed'}}aria-label="No Cast devices found"/>)}</div>)}exportdefaultCastControls
Receiver
'use client';import{useEffect,useState}from'react';import{createClient}from'@/utils/supabase/client';import{QRCodeSVG}from'qrcode.react';constsupabase=createClient();exportdefaultfunctionCastReceiverPage(){const[roomCode,setRoomCode]=useState<string|null>(null);const[participants,setParticipants]=useState<any[]>([]);useEffect(()=>{constinitializeReceiver=()=>{constcontext=window.cast?.framework?.CastReceiverContext.getInstance();if(!context){console.error('Cast Receiver Context not available');return;}// Set up custom messaging with correct namespace formatconstchannelName='urn:x-cast:com.google.cast.cac';context.addCustomMessageListener(channelName,(event: chrome.cast.framework.CustomMessageEvent)=>{const{roomCode: newRoomCode}=event.data;if(newRoomCode&&newRoomCode!==roomCode){setRoomCode(newRoomCode);// Set up Supabase subscription for the roomsetupRoomSubscription(newRoomCode);}});// Start the receiver with custom optionscontext.start({skipValidation: true,customNamespaces: {[channelName]: 'JSON'}});};constsetupRoomSubscription=async(code: string)=>{// First get the session IDconst{data: session}=awaitsupabase.from('sessions').select('id').eq('room_code',code).eq('status','active').single();if(session?.id){// Subscribe to participant changesconstchannel=supabase.channel(`session:${session.id}`).on('postgres_changes',{event: '*',schema: 'public',table: 'participants',filter: `session_id=eq.${session.id}`,},(payload)=>{if(payload.eventType==='INSERT'){setParticipants(prev=>[...prev,payload.new]);}elseif(payload.eventType==='DELETE'){setParticipants(prev=>prev.filter(p=>p.id!==payload.old.id));}elseif(payload.eventType==='UPDATE'){setParticipants(prev=>prev.map(p=>p.id===payload.new.id ? payload.new : p));}}).subscribe();// Clean up subscription when room changesreturn()=>{supabase.removeChannel(channel);};}};// Load the Cast Receiver SDKconstscript=document.createElement('script');script.src='//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js';script.onload=initializeReceiver;document.head.appendChild(script);return()=>{constcontext=window.cast?.framework?.CastReceiverContext.getInstance();if(context){context.stop();}};},[roomCode]);// Add roomCode to dependenciesif(!roomCode){return(<divclassName="flex min-h-screen flex-col items-center justify-center py-2 w-full text-white bg-black"><h1className="text-2xl font-bold">Waitingforsession...</h1></div>);}consturl=`${process.env.NEXT_PUBLIC_APP_URL}/join/${roomCode}`;return(<divclassName="flex min-h-screen flex-col items-center justify-center py-2 w-full text-black bg-white"><h1className="text-2xl font-bold mb-8">Session: {roomCode}</h1><divclassName="mb-8"><QRCodeSVGvalue={url}size={256}level="H"/></div><h2className="text-xl font-semibold mb-4">ScantoJoin</h2><pclassName="mb-8 text-lg">{url}</p>{/*Re-addedtheURLdisplay*/}<divclassName="mt-4"><h2className="text-xl font-semibold mb-4">Participants({participants.length})</h2><ulclassName="space-y-2">{participants.map((participant)=>(<likey={participant.id}className="text-lg">{participant.display_name}</li>))}</ul></div></div>);}