// -----------------------------------------------------------------------
//
// Photon Voice API Framework for Photon - Copyright (C) 2020 Exit Games GmbH
//
//
// Extends Photon Realtime API with audio streaming functionality.
//
// developer@photonengine.com
// ----------------------------------------------------------------------------
namespace Photon.Voice
{
using System;
using ExitGames.Client.Photon;
using Realtime;
///
/// Variant of LoadBalancingTransport. Aims to be non-alloc at the cost of breaking compatibility with older clients.
///
public class LoadBalancingTransport2 : LoadBalancingTransport
{
public LoadBalancingTransport2(ILogger logger = null, ConnectionProtocol connectionProtocol = ConnectionProtocol.Udp, bool cppCompatibilityMode = false) : base(logger, connectionProtocol, cppCompatibilityMode)
{
this.LoadBalancingPeer.UseByteArraySlicePoolForEvents = true; // incoming byte[] events can be deserialized to a pooled ByteArraySlice
this.LoadBalancingPeer.ReuseEventInstance = true; // this won't store references to the event anyways
}
public override int GetPayloadFragmentSize(SendFrameParams par)
{
// rough estimate, TODO: improve and test
int overhead = 3 * 2; // possible InterestGroup and Receivers: key, type, value
if (par.TargetPlayers != null)
{
overhead += 3 + par.TargetPlayers.Length; // key, type, compressed ength and array
}
return 1118 - MAX_DATA_OFFSET - overhead; // <- protocol 18 theoretical encrypted; experimental encoded: 1119, non-encrypted: 1134
}
protected override byte FrameCode => VoiceEvent.FrameCode;
const int MAX_DATA_OFFSET = 5;
protected override object buildFrameMessage(byte voiceId, byte evNumber, byte frNumber, ArraySegment data, FrameFlags flags)
{
// this uses a pooled slice, which is released within the send method (here RaiseEvent at the bottom)
ByteArraySlice frameData = this.LoadBalancingPeer.ByteArraySlicePool.Acquire(data.Count + MAX_DATA_OFFSET);
int pos = 1;
frameData.Buffer[pos++] = voiceId;
frameData.Buffer[pos++] = evNumber;
frameData.Buffer[pos++] = (byte)flags;
if (evNumber != frNumber) // save 1 byte if numbers match
{
frameData.Buffer[pos++] = (byte)frNumber;
}
frameData.Buffer[0] = (byte)pos;
Buffer.BlockCopy(data.Array, data.Offset, frameData.Buffer, pos, data.Count);
frameData.Count = data.Count + pos; // need to set the count, as we manipulated the buffer directly
return frameData;
}
protected override void onEventActionVoiceClient(EventData ev)
{
if (ev.Code == VoiceEvent.FrameCode)
{
// Payloads are arrays. If first array element is 0 than next is event subcode. Otherwise, the event is data frame with voiceId in 1st element.
this.onVoiceFrameEvent(ev[(byte)ParameterCode.CustomEventContent], REMOTE_VOICE_CHANNEL, ev.Sender, this.LocalPlayer.ActorNumber);
}
else
{
base.onEventActionVoiceClient(ev);
}
}
internal void onVoiceFrameEvent(object content0, int channelId, int playerId, int localPlayerId)
{
byte[] content;
int contentLength;
int sliceOffset = 0;
ByteArraySlice slice = content0 as ByteArraySlice;
if (slice != null)
{
content = slice.Buffer;
contentLength = slice.Count;
sliceOffset = slice.Offset;
}
else
{
content = content0 as byte[];
contentLength = content.Length;
}
if (content == null || contentLength < 3)
{
this.logger.Log(LogLevel.Error, "[PV] onVoiceFrameEvent did not receive data (readable as byte[]) " + content0);
}
else
{
byte dataOffset = (byte)content[sliceOffset];
byte voiceId = (byte)content[sliceOffset + 1];
byte evNumber = (byte)content[sliceOffset + 2];
FrameFlags flags = 0;
if (dataOffset > 3)
{
flags = (FrameFlags)content[3];
}
byte frNumber = evNumber;
if (dataOffset > 4)
{
frNumber = content[4];
}
FrameBuffer buffer;
if (slice != null)
{
buffer = new FrameBuffer(slice.Buffer, slice.Offset + dataOffset, contentLength - dataOffset, flags, frNumber, slice);
}
else
{
buffer = new FrameBuffer(content, dataOffset, contentLength - dataOffset, flags, frNumber, null);
}
this.voiceClient.onFrame(playerId, voiceId, evNumber, ref buffer, playerId == localPlayerId);
buffer.Release();
}
}
}
}