Created
January 23, 2024 21:48
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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