Skip to content

Instantly share code, notes, and snippets.

@recursivecodes
Created January 23, 2024 21:48
Show Gist options
  • Save recursivecodes/fbb04212aca22910bb4a95622f7da91b to your computer and use it in GitHub Desktop.
Save recursivecodes/fbb04212aca22910bb4a95622f7da91b to your computer and use it in GitHub Desktop.
A demo of broadcasting directly to Twitch from a game built in Unity.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.WebRTC;
using UnityEngine.Networking;
using UnityEngine.UI;
[RequireComponent(typeof(AudioListener))]
public class TwitchPublish : MonoBehaviour
{
RTCPeerConnection peerConnection;
MediaStreamTrack videoTrack;
MediaStreamTrack audioTrack;
RenderTexture renderTexture;
Texture2D screenshotTexture;
string twitchStreamKey = "";
public Button broadcastButton;
void Start()
{
StartCoroutine(WebRTC.Update());
peerConnection = new RTCPeerConnection();
peerConnection.OnIceConnectionChange = state => { Debug.Log(state); };
screenshotTexture = new Texture2D(1280, 720, TextureFormat.RGB24, false);
renderTexture = new RenderTexture(1280, 720, 24);
videoTrack = new VideoStreamTrack(renderTexture);
peerConnection.AddTrack(videoTrack);
AudioListener audioListener = GetComponent<AudioListener>();
audioTrack = new AudioStreamTrack(audioListener)
{
Loopback = true
};
peerConnection.AddTrack(audioTrack);
broadcastButton.interactable = false;
}
public void SetStreamKey(string s)
{
twitchStreamKey = s;
broadcastButton.interactable = s.Length > 0;
}
public void Broadcast()
{
StartCoroutine(DoWHIP());
}
IEnumerator RecordFrame()
{
yield return new WaitForEndOfFrame();
RenderTexture tempTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 1);
ScreenCapture.CaptureScreenshotIntoRenderTexture(tempTexture);
RenderTexture transformedTexture = RenderTexture.GetTemporary(1280, 720, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, 1);
Graphics.Blit(tempTexture, transformedTexture, new Vector2(1, -1), new Vector2(0, 1));
Graphics.Blit(transformedTexture, renderTexture);
RenderTexture.ReleaseTemporary(tempTexture);
screenshotTexture.ReadPixels(new Rect(0, 0, 1280, 720), 0, 0);
RenderTexture.ReleaseTemporary(transformedTexture);
screenshotTexture.Apply();
}
void LateUpdate()
{
StartCoroutine(RecordFrame());
}
IEnumerator DoWHIP()
{
peerConnection.AddTransceiver(TrackKind.Audio);
var offer = peerConnection.CreateOffer();
yield return offer;
var offerDesc = offer.Desc;
var opLocal = peerConnection.SetLocalDescription(ref offerDesc);
yield return opLocal;
var filteredSdp = "";
foreach (string sdpLine in offer.Desc.sdp.Split("\r\n"))
{
if (!sdpLine.StartsWith("a=extmap"))
{
filteredSdp += sdpLine + "\r\n";
}
}
using (UnityWebRequest www = new UnityWebRequest("https://g.webrtc.live-video.net:4443/v2/offer"))
{
www.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.ASCII.GetBytes(filteredSdp));
www.downloadHandler = new DownloadHandlerBuffer();
www.method = UnityWebRequest.kHttpVerbPOST;
www.SetRequestHeader("Content-Type", "application/sdp");
www.SetRequestHeader("Authorization", "Bearer " + twitchStreamKey);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.Log(JsonUtility.ToJson(www.result, true));
Debug.Log(www.error);
}
else
{
var answer = new RTCSessionDescription { type = RTCSdpType.Answer, sdp = www.downloadHandler.text };
var opRemote = peerConnection.SetRemoteDescription(ref answer);
yield return opRemote;
if (opRemote.IsError)
{
Debug.Log(opRemote.Error);
}
}
}
}
void OnDestroy()
{
if (peerConnection != null)
{
peerConnection.Close();
peerConnection.Dispose();
}
if (videoTrack != null) videoTrack.Dispose();
if (audioTrack != null) audioTrack.Dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment