In the previous tutorial, you learned how to send text messages using a DataChannel. You can have as many DataChannels as you need, up to 65535. In this tutorial, you will learn how to add an additional DataChannel to transfer binary data.
Prerequisites
This tutorial requires the Text Chat app you have built earlier.
Create File Transfer Data Channel Upstream
In the upstream connection, create a file DataChannel.
Paste the following code into the SfuUpstreamConnection function in the HelloWorldLogic class.
var dataStream = new DataStream(new DataChannel[] { dataChannelText, dataChannelFile });
// Create data stream with the data channels.
DataStream dataStream = new DataStream(new DataChannel[]{dataChannelText, dataChannelFiles});
let dataStream = FMLiveSwitchDataStream(channels: [dataChannelText!, dataChannelFile!])
// Create data stream with the data channels.
const dataStream = new fm.liveswitch.DataStream([dataChannelText, dataChannelFile]);
Send and Receive Files
Besides string, we can only send bytes through DataChannel. Therefore, to send a file, we must convert the file into bytes. To make things easier, we provide the function for converting files to bytes in our base code. Here we only need to call the function and convert the file into the following:
Number of bytes used for fileName (the first 4 bytes)
Bytes of fileName
Bytes of the file
Then, wrap them into a data buffer and send it using SendDataBytes method of the FileChannel.
When receiving files from a DataChannel, we get the databuffer by accessing the DataBytes attribute in the dataChannelReceiveArgs. Due to optimization reasons, the byte array in the data buffer we received might be different from the one we sent. It might be different in length and offset by some amount.
To get the actual content, use the following:
Index attribute in the dataBuffer which is the offset to get the actual content.
Length attribute which is the length of the original byte array.
Paste the following into the HelloWorldLogic class.
// Data channel for sending files.
private DataChannel fileChannel;
private final String fileChannelId = "file-channel";
// Event handlers to invoke when a file has been received.
public interface onNewFileReceive {
void invoke();
}
private onNewFileReceive fileReceiveEvent;
public void setFileReceiveEvent(onNewFileReceive fileReceiveEvent) {
this.fileReceiveEvent = fileReceiveEvent;
}
// Store the file information that is received temporarily.
private byte[] fileData;
private String fileName;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public byte[] getFileData() {
return fileData;
}
public void setFileData(byte[] fileData) {
this.fileData = fileData;
}
public void sendFile(InputStream fileInputStream, Cursor fileCursor) {
// Convert the file to a byte array.
byte[] fileBytes = FileTransferHelper.getFileBytes(fileInputStream);
// Convert the file name to a byte array.
byte[] fileNameBytes = FileTransferHelper.getFileNameInBytes(fileCursor);
// Convert the file name byte array's length to a byte array.
byte[] fileNameBytesLength = FileTransferHelper.getFileNameSizeInBytes(fileCursor);
// Wrap the file into a data buffer.
DataBuffer dataBuffer = DataBuffer.wrap(fileBytes);
// Prepend the byte arrays to the data buffer.
dataBuffer.prepend(DataBuffer.wrap(fileNameBytes));
dataBuffer.prepend(DataBuffer.wrap(fileNameBytesLength));
// Send the data buffer through the data channel.
fileChannel.sendDataBytes(dataBuffer);
}
public void onFileReceive(DataChannelReceiveArgs dataChannelReceiveArgs) {
// Retrieve the data buffer.
DataBuffer dataBuffer = dataChannelReceiveArgs.getDataBytes();
// Get the length of the file name.
int fileNameLength = FileTransferHelper.getIntFromBytes(dataBuffer.getData(), dataBuffer.getIndex());
// Retrieve the file name.
this.fileName = FileTransferHelper.getStringFromBytes(
dataBuffer.getData(),
dataBuffer.getIndex() + 4,
fileNameLength);
// Get the file size.
int length = dataBuffer.getLength() - 4 - fileNameLength;
// Retrieve the file's data.
this.fileData = Arrays.copyOfRange(dataBuffer.getData(), dataBuffer.getIndex() + 4 + fileNameLength, length);
fileReceiveEvent.invoke();
}
var _fileChannel: FMLiveSwitchDataChannel?
var _fileChannelId: String = "file-channel"
var _onFileReceiveAction: (() -> Void)? = nil
var _fileBytes: Data = Data.init()
var _fileName: String = ""
func setOnFileReceiveAction(action: @escaping () -> Void) {
self._onFileReceiveAction = action
}
func sendDataBytes(fileName: String, fileNameSize: Int, fileBytes: Data) {
let dataBuffer = FMLiveSwitchDataBuffer.wrap(with: fileBytes.toNSMutableData())
dataBuffer?.prepend(with: FMLiveSwitchDataBuffer.wrap(with: fileName.toNSMutableData()))
dataBuffer?.prepend(with: FMLiveSwitchDataBuffer.wrap(with:(Int32(fileNameSize)).toNSMutableData()))
_fileChannel?.sendDataBytes(dataBuffer)
}
func onReceiveDataBytes(args: FMLiveSwitchDataChannelReceiveArgs) {
let buffer = args.dataBytes()
let bufferData = buffer?.data()
let bufferIndex: Int = Int((buffer?.index())!)
let fileNameSize = bufferData?.toInt(offset: bufferIndex)
let fileName = bufferData?.toString(offset: bufferIndex + 4, length: fileNameSize!)
let totalBytesLength = bufferData!.length - 4 - fileNameSize! - bufferIndex
let fileIndex = bufferIndex + 4 + fileNameSize!
let fileBytes = bufferData?.toData().subdata(in: fileIndex..<(fileIndex + totalBytesLength))
self._fileName = fileName!
self._fileBytes = fileBytes!
self._onFileReceiveAction!()
}
func getDataBytes() -> Data {
return self._fileBytes
}
func getFileName() -> String {
return self._fileName
}
private fileChannel: fm.liveswitch.DataChannel;
public sendFile(fileName: string, file: Uint8Array): void {
// Convert the file name to a byte array.
const fileNameBytes: Uint8Array = DataBytesConverter.toDataBytes(fileName);
// Convert the file name byte array's length to a byte array.
const fileNameBytesLength: Uint8Array = DataBytesConverter.intToDataBytes(fileNameBytes.length);
// Wrap the file into a data buffer.
const dataBuffer: fm.liveswitch.DataBuffer = fm.liveswitch.DataBuffer.wrap(file);
// Prepend the byte arrays to the data buffer.
dataBuffer.prepend(fm.liveswitch.DataBuffer.wrap(fileNameBytes));
dataBuffer.prepend(fm.liveswitch.DataBuffer.wrap(fileNameBytesLength));
// Send the data buffer through the data channel.
this.fileChannel.sendDataBytes(dataBuffer);
}
private onFileReceive(dataChannelReceiveArgs: fm.liveswitch.DataChannelReceiveArgs): void {
// Retrieve the data buffer.
const dataBuffer: fm.liveswitch.DataBuffer = dataChannelReceiveArgs.getDataBytes();
// Get the offset to the actual data.
const dataOffset: number = dataBuffer.getIndex();
// Get the file name byte array's length and retrieve the file name.
const fileNameBytesLength: number = DataBytesConverter.toInt(dataBuffer.getData().slice(dataOffset));
const fileName: string = DataBytesConverter.toString(dataBuffer.getData().slice(dataOffset + 4, dataOffset + 4 + fileNameBytesLength));
// Get the offset to the file's data and the file size.
const fileOffset: number = dataOffset + 4 + fileNameBytesLength;
const fileSize: number = dataBuffer.getLength() - 4 - fileNameBytesLength;
// Retrieve the file's data using the offset and the file's size.
const dataBytes: Uint8Array = dataBuffer.getData().slice(fileOffset, fileOffset + fileSize);
// Save the file.
this.saveFile(fileName, dataBytes);
}
Uncomment UI Components
Now go to the files for the UI components and uncomment the code for sharing screen.
In the MainWindow.xaml.cs file, uncomment the codes between the <File Transfer> and </File Transfer> tags.
In the OnFileReceiveFragment.java file, uncomment all the code that has been commented out.
In the FileSelectionFragment.java file, uncomment all the code that has been commented out.
In the MainActivity.java file, uncomment all the code between the <FileTransfer> and </FileTransfer> tags.
In ViewModel.swift file, uncomment code between the <FileTransfer> and </FileTransfer> tags.
In the ContentView.swift file, uncomment code between the <FileTransfer> and </FileTransfer> tags.
In FileTransferUI.swift, uncomment all the code that has been commented out.
In the index.ts file, uncomment the codes between the <File Transfer> and </File Transfer> tags.
Run Your App
Tip
In the mobile app, some buttons are hidden. To show hidden buttons, swipe the button (not the screen) from left to right.
Run your app in your project IDE on multiple devices. In the first device, click Join and then click the File icon to select a file to upload. In the second device, you should be notified of the receiving file message. Select to accept the file and you should be able to save the file to your device. Congratulations, you've learned how to transfer binary data using the DataChannel.