2026-02-22 14:21:56 +01:00

220 lines
6.7 KiB
C#

using UnityEngine;
using ExitGames.Client.Photon;
using Photon.Realtime;
using Photon.Voice;
using System;
using System.Threading;
namespace DataStreamDemo
{
public class DataStreamClient : MonoBehaviour
{
public string AppId;
public string AppVersion = "1";
public string Region = "EU";
public const string RoomName = "PhotonDataStream";
public Photon.Voice.LogLevel LogLevel = Photon.Voice.LogLevel.Info;
[Space(10)]
public int FPS = 30;
public int FrameSize = 2000;
public int DecodeDelayFrames = 3;
public bool Echo;
Codec DataStreamCodec = Codec.Custom1;
bool started;
ByteStreamEncoder encoder;
LocalVoice localDataStream;
// Separate media in channels for better Photon transport performance
enum Channel
{
DataStream = 1
}
public VoiceClient VoiceClient => lbt.VoiceClient;
LoadBalancingTransport lbt;
protected Photon.Voice.Unity.Logger logger = new Photon.Voice.Unity.Logger();
protected virtual void Start()
{
logger.Level = LogLevel;
lbt = new LoadBalancingTransport2(logger);
lbt.LoadBalancingPeer.DebugOut = DebugLevel.INFO;
lbt.LoadBalancingPeer.TrafficStatsEnabled = true;
lbt.AppId = AppId;
lbt.AppVersion = AppVersion;
lbt.StateChanged += (ClientState stateOld, ClientState s) =>
{
logger.Log(LogLevel.Info, $"LBC: state: {s}");
switch (s)
{
case ClientState.ConnectedToMasterServer:
lbt.OpJoinRandomOrCreateRoom(null, new EnterRoomParams()
{
RoomName = RoomName,
RoomOptions = new RoomOptions() { MaxPlayers = 5 } // the UI limits the number of incoming video streams to 4
});
break;
case ClientState.Joined:
// recreate voices to update from settings possibly changed by user in lobby
CreateDataStream();
break;
case ClientState.Disconnected:
RemoveDataStream();
break;
}
};
VoiceClient.SetRemoteVoiceDelayFrames(DataStreamCodec, DecodeDelayFrames);
VoiceClient.OnRemoteVoiceInfoAction += OnRemoteVoiceAdd;
Connect();
Debug.LogFormat("LBC: init");
started = true;
}
protected virtual void Update()
{
if (!started)
{
return;
}
// apply settings dynamically
VoiceClient.SetRemoteVoiceDelayFrames(DataStreamCodec, DecodeDelayFrames);
if (localDataStream != null)
{
this.localDataStream.DebugEchoMode = Echo;
}
lbt.Service();
}
protected void OnApplicationQuit()
{
this.Disconnect();
}
public void Connect()
{
lbt.ConnectToRegionMaster(Region);
}
public void Disconnect()
{
if (lbt != null)
{
lbt.Disconnect();
}
}
// Called by VoiceClient for every new stream
private void OnRemoteVoiceAdd(int channelId, int playerId, byte voiceId, VoiceInfo i, ref RemoteVoiceOptions options)
{
if (i.Codec == DataStreamCodec)
{
options.Decoder = new ByteStreamDecoder(consumeDecoderOutput, () => Debug.LogWarning("Decoder missing frame"));
}
else
{
Debug.LogErrorFormat("LBC: " + "unsupported codec " + i.Codec);
}
}
bool streaming = false;
protected void CreateDataStream()
{
var voiceInfo = new VoiceInfo()
{
Codec = DataStreamCodec
};
encoder = new ByteStreamEncoder();
var options = new VoiceCreateOptions()
{
DebugEchoMode = Echo,
Encoder = encoder,
EventBufSize = 4 * 256, // receiving buffer of increased size
Fragment = true,
Reliable = true,
};
localDataStream = VoiceClient.CreateLocalVoice(voiceInfo, (int)Channel.DataStream, options);
new Thread(produceEncoderInput).Start();
}
protected void RemoveDataStream()
{
streaming = false;
if (this.localDataStream != null)
{
this.localDataStream?.RemoveSelf();
}
}
int cnt = 0;
int nextReport = Environment.TickCount + 1000;
int sent = 0;
void produceEncoderInput()
{
Debug.LogFormat("Streaming start");
streaming = true;
byte[] buf = null;
System.Random rand = new System.Random();
while (streaming)
{
if (lbt.State == ClientState.Joined)
{
if (buf == null || buf.Length != FrameSize + 4)
{
buf = new byte[FrameSize + 4];
}
rand.NextBytes(buf);
var hash = BitConverter.GetBytes(Util.CalculateCrc(buf, 0, buf.Length - 4));
Array.Copy(hash, 0, buf, buf.Length - 4, 4);
encoder.Input(buf);
sent++;
}
Thread.Sleep(1000 / FPS);
}
Debug.LogFormat("Streaming stop");
}
// Normally called from Decoder Input() in a worker thread.
// Use buf.Retain() / Release() if you need buf to be valid after return.
void consumeDecoderOutput(ref FrameBuffer buf)
{
var hash = Util.CalculateCrc(buf.Array, buf.Offset, buf.Length - 4);
if (hash != BitConverter.ToUInt32(buf.Array, buf.Offset + buf.Length - 4))
{
Debug.LogErrorFormat("Decoder corrupted frame, FrameSize: {0}, buf len: {1}", FrameSize, buf.Length);
}
cnt += buf.Length;
var t = Environment.TickCount;
if (t - nextReport > 0)
{
Debug.LogFormat("Decoder received {0} bytes/sec, FrameSize: {1}, buf len: {2}", cnt, FrameSize, buf.Length);
cnt = 0;
nextReport = t + 1000;
}
}
private void OnGUI()
{
GUILayout.Label("Sent: " + sent);
}
}
}