#include "Limelight-internal.h" #include "Platform.h" static int stage = STAGE_NONE; static ConnListenerConnectionTerminated originalTerminationCallback; static int alreadyTerminated; static PLT_THREAD terminationCallbackThread; static long terminationCallbackErrorCode; // Common globals char* RemoteAddrString; struct sockaddr_storage RemoteAddr; SOCKADDR_LEN RemoteAddrLen; int AppVersionQuad[4]; STREAM_CONFIGURATION StreamConfig; CONNECTION_LISTENER_CALLBACKS ListenerCallbacks; DECODER_RENDERER_CALLBACKS VideoCallbacks; AUDIO_RENDERER_CALLBACKS AudioCallbacks; int NegotiatedVideoFormat; // Connection stages static const char* stageNames[STAGE_MAX] = { "none", "platform initialization", "name resolution", "RTSP handshake", "control stream initialization", "video stream initialization", "audio stream initialization", "input stream initialization", "control stream establishment", "video stream establishment", "audio stream establishment", "input stream establishment" }; // Get the name of the current stage based on its number const char* LiGetStageName(int stage) { return stageNames[stage]; } // Stop the connection by undoing the step at the current stage and those before it void LiStopConnection(void) { // Disable termination callbacks now alreadyTerminated = 1; if (stage == STAGE_INPUT_STREAM_START) { Limelog("Stopping input stream..."); stopInputStream(); stage--; Limelog("done\n"); } if (stage == STAGE_AUDIO_STREAM_START) { Limelog("Stopping audio stream..."); stopAudioStream(); stage--; Limelog("done\n"); } if (stage == STAGE_VIDEO_STREAM_START) { Limelog("Stopping video stream..."); stopVideoStream(); stage--; Limelog("done\n"); } if (stage == STAGE_CONTROL_STREAM_START) { Limelog("Stopping control stream..."); stopControlStream(); stage--; Limelog("done\n"); } if (stage == STAGE_INPUT_STREAM_INIT) { Limelog("Cleaning up input stream..."); destroyInputStream(); stage--; Limelog("done\n"); } if (stage == STAGE_AUDIO_STREAM_INIT) { Limelog("Cleaning up audio stream..."); destroyAudioStream(); stage--; Limelog("done\n"); } if (stage == STAGE_VIDEO_STREAM_INIT) { Limelog("Cleaning up video stream..."); destroyVideoStream(); stage--; Limelog("done\n"); } if (stage == STAGE_CONTROL_STREAM_INIT) { Limelog("Cleaning up control stream..."); destroyControlStream(); stage--; Limelog("done\n"); } if (stage == STAGE_RTSP_HANDSHAKE) { Limelog("Terminating RTSP handshake..."); terminateRtspHandshake(); stage--; Limelog("done\n"); } if (stage == STAGE_NAME_RESOLUTION) { // Nothing to do stage--; } if (stage == STAGE_PLATFORM_INIT) { Limelog("Cleaning up platform..."); cleanupPlatform(); stage--; Limelog("done\n"); } LC_ASSERT(stage == STAGE_NONE); if (RemoteAddrString != NULL) { free(RemoteAddrString); RemoteAddrString = NULL; } } static void terminationCallbackThreadFunc(void* context) { // Invoke the client's termination callback originalTerminationCallback(terminationCallbackErrorCode); } // This shim callback runs the client's connectionTerminated() callback on a // separate thread. This is neccessary because other internal threads directly // invoke this callback. That can result in a deadlock if the client // calls LiStopConnection() in the callback when the cleanup code // attempts to join the thread that the termination callback (and LiStopConnection) // is running on. static void ClInternalConnectionTerminated(long errorCode) { int err; // Avoid recursion and issuing multiple callbacks if (alreadyTerminated) { return; } alreadyTerminated = 1; // Invoke the termination callback on a separate thread err = PltCreateThread(terminationCallbackThreadFunc, NULL, &terminationCallbackThread); if (err != 0) { // Nothing we can safely do here, so we'll just assert on debug builds Limelog("Failed to create termination thread: %d\n", err); LC_ASSERT(err == 0); } // Close the thread handle since we can never wait on it PltCloseThread(&terminationCallbackThread); } static int resolveHostName(const char* host) { #ifndef __vita__ struct addrinfo hints, *res; int err; // We must first try IPv4-only because GFE doesn't listen on IPv6, // so we'll only want to use an IPv6 address if it's the only address we have. // For NAT64 networks, the IPv4 address resolution will fail but the IPv6 address // will give us working connectivity to the host. All other networks will use IPv4 // addresses. memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_flags = AI_ADDRCONFIG; err = getaddrinfo(host, NULL, &hints, &res); if (err != 0 || res == NULL) { memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_ADDRCONFIG; err = getaddrinfo(host, NULL, &hints, &res); if (err != 0) { Limelog("getaddrinfo() failed: %d\n", err); return err; } if (res == NULL) { Limelog("getaddrinfo() returned success without addresses\n"); return -1; } } // Use the first address in the list memcpy(&RemoteAddr, res->ai_addr, res->ai_addrlen); RemoteAddrLen = res->ai_addrlen; freeaddrinfo(res); return 0; #else struct hostent *phost = gethostbyname(host); if (!phost) { Limelog("gethostbyname() failed for host %s\n", host); return -1; } struct sockaddr_in tmp = {0}; tmp.sin_len = sizeof(tmp); tmp.sin_family = SCE_NET_AF_INET; memcpy(&tmp.sin_addr, phost->h_addr, phost->h_length); memcpy(&RemoteAddr, &tmp, sizeof(tmp)); RemoteAddrLen = sizeof(tmp); return 0; #endif } // Starts the connection to the streaming machine int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags) { int err; NegotiatedVideoFormat = 0; memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig)); RemoteAddrString = strdup(serverInfo->address); // Extract the appversion from the supplied string if (extractVersionQuadFromString(serverInfo->serverInfoAppVersion, AppVersionQuad) < 0) { Limelog("Invalid appversion string: %s\n", serverInfo->serverInfoAppVersion); return -1; } // Replace missing callbacks with placeholders fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks); memcpy(&VideoCallbacks, drCallbacks, sizeof(VideoCallbacks)); memcpy(&AudioCallbacks, arCallbacks, sizeof(AudioCallbacks)); // Hook the termination callback so we can avoid issuing a termination callback // after LiStopConnection() is called originalTerminationCallback = clCallbacks->connectionTerminated; memcpy(&ListenerCallbacks, clCallbacks, sizeof(ListenerCallbacks)); ListenerCallbacks.connectionTerminated = ClInternalConnectionTerminated; alreadyTerminated = 0; Limelog("Initializing platform..."); ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT); err = initializePlatform(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_PLATFORM_INIT, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_PLATFORM_INIT); ListenerCallbacks.stageComplete(STAGE_PLATFORM_INIT); Limelog("done\n"); Limelog("Resolving host name..."); ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION); err = resolveHostName(serverInfo->address); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_NAME_RESOLUTION); ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION); Limelog("done\n"); Limelog("Starting RTSP handshake..."); ListenerCallbacks.stageStarting(STAGE_RTSP_HANDSHAKE); err = performRtspHandshake(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_RTSP_HANDSHAKE, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_RTSP_HANDSHAKE); ListenerCallbacks.stageComplete(STAGE_RTSP_HANDSHAKE); Limelog("done\n"); Limelog("Initializing control stream..."); ListenerCallbacks.stageStarting(STAGE_CONTROL_STREAM_INIT); err = initializeControlStream(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_CONTROL_STREAM_INIT, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_CONTROL_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_CONTROL_STREAM_INIT); Limelog("done\n"); Limelog("Initializing video stream..."); ListenerCallbacks.stageStarting(STAGE_VIDEO_STREAM_INIT); initializeVideoStream(); stage++; LC_ASSERT(stage == STAGE_VIDEO_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_INIT); Limelog("done\n"); Limelog("Initializing audio stream..."); ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_INIT); initializeAudioStream(); stage++; LC_ASSERT(stage == STAGE_AUDIO_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_INIT); Limelog("done\n"); Limelog("Initializing input stream..."); ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_INIT); initializeInputStream(); stage++; LC_ASSERT(stage == STAGE_INPUT_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_INIT); Limelog("done\n"); Limelog("Starting control stream..."); ListenerCallbacks.stageStarting(STAGE_CONTROL_STREAM_START); err = startControlStream(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_CONTROL_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_CONTROL_STREAM_START); ListenerCallbacks.stageComplete(STAGE_CONTROL_STREAM_START); Limelog("done\n"); Limelog("Starting video stream..."); ListenerCallbacks.stageStarting(STAGE_VIDEO_STREAM_START); err = startVideoStream(renderContext, drFlags); if (err != 0) { Limelog("Video stream start failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_VIDEO_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_VIDEO_STREAM_START); ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_START); Limelog("done\n"); Limelog("Starting audio stream..."); ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_START); err = startAudioStream(); if (err != 0) { Limelog("Audio stream start failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_AUDIO_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_AUDIO_STREAM_START); ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_START); Limelog("done\n"); Limelog("Starting input stream..."); ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_START); err = startInputStream(); if (err != 0) { Limelog("Input stream start failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_INPUT_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_INPUT_STREAM_START); ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_START); Limelog("done\n"); // Wiggle the mouse a bit to wake the display up LiSendMouseMoveEvent(1, 1); PltSleepMs(10); LiSendMouseMoveEvent(-1, -1); PltSleepMs(10); ListenerCallbacks.connectionStarted(); Cleanup: return err; }