라이브 방 - 아고라 설정 및 라이브 방 관련 API
This commit is contained in:
171
src/main/kotlin/kr/co/vividnext/sodalive/agora/AccessToken.kt
Normal file
171
src/main/kotlin/kr/co/vividnext/sodalive/agora/AccessToken.kt
Normal file
@@ -0,0 +1,171 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.TreeMap
|
||||
|
||||
class AccessToken(
|
||||
var appId: String,
|
||||
private val appCertificate: String,
|
||||
val channelName: String,
|
||||
private val uid: String,
|
||||
var crcChannelName: Int = 0,
|
||||
private var crcUid: Int = 0,
|
||||
val message: PrivilegeMessage = PrivilegeMessage()
|
||||
) {
|
||||
|
||||
private lateinit var signature: ByteArray
|
||||
private lateinit var messageRawContent: ByteArray
|
||||
|
||||
enum class Privileges(value: Int) {
|
||||
JoinChannel(1),
|
||||
PublishAudioStream(2),
|
||||
PublishVideoStream(3),
|
||||
PublishDataStream(4), // For RTM only
|
||||
RtmLogin(1000);
|
||||
|
||||
var intValue: Short
|
||||
|
||||
init {
|
||||
intValue = value.toShort()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun build(): String {
|
||||
if (!AgoraUtils.isUUID(appId)) {
|
||||
return ""
|
||||
}
|
||||
if (!AgoraUtils.isUUID(appCertificate)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
messageRawContent = AgoraUtils.pack(message)
|
||||
signature = generateSignature(
|
||||
appCertificate,
|
||||
appId,
|
||||
channelName,
|
||||
uid,
|
||||
messageRawContent
|
||||
)
|
||||
crcChannelName = AgoraUtils.crc32(channelName)
|
||||
crcUid = AgoraUtils.crc32(uid)
|
||||
val packContent = PackContent(signature, crcChannelName, crcUid, messageRawContent)
|
||||
val content: ByteArray = AgoraUtils.pack(packContent)
|
||||
return getVersion() + appId + AgoraUtils.base64Encode(content)
|
||||
}
|
||||
|
||||
fun addPrivilege(privilege: Privileges, expireTimestamp: Int) {
|
||||
message.messages[privilege.intValue] = expireTimestamp
|
||||
}
|
||||
|
||||
private fun getVersion(): String {
|
||||
return VER
|
||||
}
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
fun generateSignature(
|
||||
appCertificate: String,
|
||||
appID: String,
|
||||
channelName: String,
|
||||
uid: String,
|
||||
message: ByteArray
|
||||
): ByteArray {
|
||||
val baos = ByteArrayOutputStream()
|
||||
|
||||
try {
|
||||
baos.write(appID.toByteArray())
|
||||
baos.write(channelName.toByteArray())
|
||||
baos.write(uid.toByteArray())
|
||||
baos.write(message)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return AgoraUtils.hmacSign(appCertificate, baos.toByteArray())
|
||||
}
|
||||
|
||||
fun fromString(token: String): Boolean {
|
||||
if (getVersion() != token.substring(0, AgoraUtils.VERSION_LENGTH)) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
appId = token.substring(AgoraUtils.VERSION_LENGTH, AgoraUtils.VERSION_LENGTH + AgoraUtils.APP_ID_LENGTH)
|
||||
val packContent = PackContent()
|
||||
AgoraUtils.unpack(
|
||||
AgoraUtils.base64Decode(
|
||||
token.substring(
|
||||
AgoraUtils.VERSION_LENGTH + AgoraUtils.APP_ID_LENGTH,
|
||||
token.length
|
||||
)
|
||||
),
|
||||
packContent
|
||||
)
|
||||
signature = packContent.signature
|
||||
crcChannelName = packContent.crcChannelName
|
||||
crcUid = packContent.crcUid
|
||||
messageRawContent = packContent.rawMessage
|
||||
AgoraUtils.unpack(messageRawContent, message)
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
class PrivilegeMessage : PackableEx {
|
||||
var salt: Int
|
||||
var ts: Int
|
||||
var messages: TreeMap<Short, Int>
|
||||
|
||||
override fun marshal(out: ByteBuf): ByteBuf {
|
||||
return out.put(salt).put(ts).putIntMap(messages)
|
||||
}
|
||||
|
||||
override fun unmarshal(input: ByteBuf) {
|
||||
salt = input.readInt()
|
||||
ts = input.readInt()
|
||||
messages = input.readIntMap()
|
||||
}
|
||||
|
||||
init {
|
||||
salt = AgoraUtils.randomInt()
|
||||
ts = AgoraUtils.getTimestamp() + 24 * 3600
|
||||
messages = TreeMap()
|
||||
}
|
||||
}
|
||||
|
||||
class PackContent() : PackableEx {
|
||||
var signature: ByteArray = byteArrayOf()
|
||||
var crcChannelName = 0
|
||||
var crcUid = 0
|
||||
var rawMessage: ByteArray = byteArrayOf()
|
||||
|
||||
constructor(signature: ByteArray, crcChannelName: Int, crcUid: Int, rawMessage: ByteArray) : this() {
|
||||
this.signature = signature
|
||||
this.crcChannelName = crcChannelName
|
||||
this.crcUid = crcUid
|
||||
this.rawMessage = rawMessage
|
||||
}
|
||||
|
||||
override fun marshal(out: ByteBuf): ByteBuf {
|
||||
return out
|
||||
.put(signature)
|
||||
.put(crcChannelName)
|
||||
.put(crcUid).put(rawMessage)
|
||||
}
|
||||
|
||||
override fun unmarshal(input: ByteBuf) {
|
||||
signature = input.readBytes()
|
||||
crcChannelName = input.readInt()
|
||||
crcUid = input.readInt()
|
||||
rawMessage = input.readBytes()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val VER = "006"
|
||||
}
|
||||
}
|
72
src/main/kotlin/kr/co/vividnext/sodalive/agora/AgoraUtils.kt
Normal file
72
src/main/kotlin/kr/co/vividnext/sodalive/agora/AgoraUtils.kt
Normal file
@@ -0,0 +1,72 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.util.Date
|
||||
import java.util.zip.CRC32
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object AgoraUtils {
|
||||
const val HMAC_SHA256_LENGTH: Long = 32
|
||||
const val VERSION_LENGTH = 3
|
||||
const val APP_ID_LENGTH = 32
|
||||
|
||||
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class)
|
||||
fun hmacSign(keyString: String, msg: ByteArray?): ByteArray {
|
||||
val keySpec = SecretKeySpec(keyString.toByteArray(), "HmacSHA256")
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(keySpec)
|
||||
return mac.doFinal(msg)
|
||||
}
|
||||
|
||||
fun pack(packableEx: PackableEx): ByteArray {
|
||||
val buffer = ByteBuf()
|
||||
packableEx.marshal(buffer)
|
||||
return buffer.asBytes()
|
||||
}
|
||||
|
||||
fun unpack(data: ByteArray?, packableEx: PackableEx) {
|
||||
val buffer = ByteBuf(data!!)
|
||||
packableEx.unmarshal(buffer)
|
||||
}
|
||||
|
||||
fun base64Encode(data: ByteArray?): String {
|
||||
val encodedBytes: ByteArray = Base64.encodeBase64(data)
|
||||
return String(encodedBytes)
|
||||
}
|
||||
|
||||
fun base64Decode(data: String): ByteArray {
|
||||
return Base64.decodeBase64(data.toByteArray())
|
||||
}
|
||||
|
||||
fun crc32(data: String): Int {
|
||||
// get bytes from string
|
||||
val bytes = data.toByteArray()
|
||||
return crc32(bytes)
|
||||
}
|
||||
|
||||
fun crc32(bytes: ByteArray?): Int {
|
||||
val checksum = CRC32()
|
||||
checksum.update(bytes)
|
||||
return checksum.value.toInt()
|
||||
}
|
||||
|
||||
fun getTimestamp(): Int {
|
||||
return (Date().time / 1000).toInt()
|
||||
}
|
||||
|
||||
fun randomInt(): Int {
|
||||
return SecureRandom().nextInt()
|
||||
}
|
||||
|
||||
fun isUUID(uuid: String): Boolean {
|
||||
return if (uuid.length != 32) {
|
||||
false
|
||||
} else {
|
||||
uuid.matches("\\p{XDigit}+".toRegex())
|
||||
}
|
||||
}
|
||||
}
|
111
src/main/kotlin/kr/co/vividnext/sodalive/agora/ByteBuf.kt
Normal file
111
src/main/kotlin/kr/co/vividnext/sodalive/agora/ByteBuf.kt
Normal file
@@ -0,0 +1,111 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.TreeMap
|
||||
|
||||
class ByteBuf() {
|
||||
private var buffer = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
constructor(bytes: ByteArray) : this() {
|
||||
this.buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
|
||||
}
|
||||
|
||||
fun asBytes(): ByteArray {
|
||||
val out = ByteArray(buffer.position())
|
||||
buffer.rewind()
|
||||
buffer[out, 0, out.size]
|
||||
return out
|
||||
}
|
||||
|
||||
// packUint16
|
||||
fun put(v: Short): ByteBuf {
|
||||
buffer.putShort(v)
|
||||
return this
|
||||
}
|
||||
|
||||
fun put(v: ByteArray): ByteBuf {
|
||||
put(v.size.toShort())
|
||||
buffer.put(v)
|
||||
return this
|
||||
}
|
||||
|
||||
// packUint32
|
||||
fun put(v: Int): ByteBuf {
|
||||
buffer.putInt(v)
|
||||
return this
|
||||
}
|
||||
|
||||
fun put(v: Long): ByteBuf {
|
||||
buffer.putLong(v)
|
||||
return this
|
||||
}
|
||||
|
||||
fun put(v: String): ByteBuf {
|
||||
return put(v.toByteArray())
|
||||
}
|
||||
|
||||
fun put(extra: TreeMap<Short, String>): ByteBuf {
|
||||
put(extra.size.toShort())
|
||||
for ((key, value) in extra.entries) {
|
||||
put(key)
|
||||
put(value)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun putIntMap(extra: TreeMap<Short, Int>): ByteBuf {
|
||||
put(extra.size.toShort())
|
||||
for ((key, value) in extra.entries) {
|
||||
put(key)
|
||||
put(value)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun readShort(): Short {
|
||||
return buffer.short
|
||||
}
|
||||
|
||||
fun readInt(): Int {
|
||||
return buffer.int
|
||||
}
|
||||
|
||||
fun readBytes(): ByteArray {
|
||||
val length = readShort()
|
||||
val bytes = ByteArray(length.toInt())
|
||||
buffer[bytes]
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun readString(): String {
|
||||
val bytes = readBytes()
|
||||
return String(bytes)
|
||||
}
|
||||
|
||||
fun readMap(): TreeMap<Short, String> {
|
||||
val map = TreeMap<Short, String>()
|
||||
val length = readShort()
|
||||
|
||||
for (i in 0 until length) {
|
||||
val k = readShort()
|
||||
val v = readString()
|
||||
map[k] = v
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
fun readIntMap(): TreeMap<Short, Int> {
|
||||
val map = TreeMap<Short, Int>()
|
||||
val length = readShort()
|
||||
|
||||
for (i in 0 until length) {
|
||||
val k = readShort()
|
||||
val v = readInt()
|
||||
map[k] = v
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
}
|
256
src/main/kotlin/kr/co/vividnext/sodalive/agora/DynamicKey5.kt
Normal file
256
src/main/kotlin/kr/co/vividnext/sodalive/agora/DynamicKey5.kt
Normal file
@@ -0,0 +1,256 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import java.util.TreeMap
|
||||
|
||||
class DynamicKey5 {
|
||||
lateinit var content: DynamicKey5Content
|
||||
|
||||
fun fromString(key: String): Boolean {
|
||||
if (key.substring(0, 3) != version) {
|
||||
return false
|
||||
}
|
||||
val rawContent: ByteArray = Base64().decode(key.substring(3))
|
||||
if (rawContent.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
content = DynamicKey5Content()
|
||||
val buffer = ByteBuf(rawContent)
|
||||
content.unmarshall(buffer)
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val version = "005"
|
||||
const val noUpload = "0"
|
||||
const val audioVideoUpload = "3"
|
||||
|
||||
// ServiceType
|
||||
const val MEDIA_CHANNEL_SERVICE: Short = 1
|
||||
const val RECORDING_SERVICE: Short = 2
|
||||
const val PUBLIC_SHARING_SERVICE: Short = 3
|
||||
const val IN_CHANNEL_PERMISSION: Short = 4
|
||||
|
||||
// InChannelPermissionKey
|
||||
const val ALLOW_UPLOAD_IN_CHANNEL: Short = 1
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun generateSignature(
|
||||
appCertificate: String,
|
||||
service: Short,
|
||||
appID: String,
|
||||
unixTs: Int,
|
||||
salt: Int,
|
||||
channelName: String,
|
||||
uid: Long,
|
||||
expiredTs: Int,
|
||||
extra: TreeMap<Short, String>
|
||||
): String {
|
||||
// decode hex to avoid case problem
|
||||
val hex = Hex()
|
||||
val rawAppID: ByteArray = hex.decode(appID.toByteArray())
|
||||
val rawAppCertificate: ByteArray = hex.decode(appCertificate.toByteArray())
|
||||
val m = Message(
|
||||
service,
|
||||
rawAppID,
|
||||
unixTs,
|
||||
salt,
|
||||
channelName,
|
||||
(uid and 0xFFFFFFFFL).toInt(),
|
||||
expiredTs,
|
||||
extra
|
||||
)
|
||||
val toSign: ByteArray = pack(m)
|
||||
return String(Hex.encodeHex(DynamicKeyUtil.encodeHMAC(rawAppCertificate, toSign), false))
|
||||
}
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
fun generateDynamicKey(
|
||||
appID: String,
|
||||
appCertificate: String,
|
||||
channel: String,
|
||||
ts: Int,
|
||||
salt: Int,
|
||||
uid: Long,
|
||||
expiredTs: Int,
|
||||
extra: TreeMap<Short, String>,
|
||||
service: Short
|
||||
): String {
|
||||
val signature = generateSignature(appCertificate, service, appID, ts, salt, channel, uid, expiredTs, extra)
|
||||
val content =
|
||||
DynamicKey5Content(service, signature, Hex().decode(appID.toByteArray()), ts, salt, expiredTs, extra)
|
||||
val bytes: ByteArray = pack(content)
|
||||
val encoded = Base64().encode(bytes)
|
||||
val base64 = String(encoded)
|
||||
return version + base64
|
||||
}
|
||||
|
||||
private fun pack(content: Packable): ByteArray {
|
||||
val buffer = ByteBuf()
|
||||
content.marshal(buffer)
|
||||
return buffer.asBytes()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun generatePublicSharingKey(
|
||||
appID: String,
|
||||
appCertificate: String,
|
||||
channel: String,
|
||||
ts: Int,
|
||||
salt: Int,
|
||||
uid: Long,
|
||||
expiredTs: Int
|
||||
) = generateDynamicKey(
|
||||
appID,
|
||||
appCertificate,
|
||||
channel,
|
||||
ts,
|
||||
salt,
|
||||
uid,
|
||||
expiredTs,
|
||||
TreeMap(),
|
||||
PUBLIC_SHARING_SERVICE
|
||||
)
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun generateRecordingKey(
|
||||
appID: String,
|
||||
appCertificate: String,
|
||||
channel: String,
|
||||
ts: Int,
|
||||
salt: Int,
|
||||
uid: Long,
|
||||
expiredTs: Int
|
||||
) = generateDynamicKey(
|
||||
appID,
|
||||
appCertificate,
|
||||
channel,
|
||||
ts,
|
||||
salt,
|
||||
uid,
|
||||
expiredTs,
|
||||
TreeMap(),
|
||||
RECORDING_SERVICE
|
||||
)
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun generateMediaChannelKey(
|
||||
appID: String,
|
||||
appCertificate: String,
|
||||
channel: String,
|
||||
ts: Int,
|
||||
salt: Int,
|
||||
uid: Long,
|
||||
expiredTs: Int
|
||||
) = generateDynamicKey(
|
||||
appID,
|
||||
appCertificate,
|
||||
channel,
|
||||
ts,
|
||||
salt,
|
||||
uid,
|
||||
expiredTs,
|
||||
TreeMap(),
|
||||
MEDIA_CHANNEL_SERVICE
|
||||
)
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun generateInChannelPermissionKey(
|
||||
appID: String,
|
||||
appCertificate: String,
|
||||
channel: String,
|
||||
ts: Int,
|
||||
salt: Int,
|
||||
uid: Long,
|
||||
expiredTs: Int,
|
||||
permission: String
|
||||
): String {
|
||||
val extra = TreeMap<Short, String>()
|
||||
extra[ALLOW_UPLOAD_IN_CHANNEL] = permission
|
||||
return generateDynamicKey(
|
||||
appID,
|
||||
appCertificate,
|
||||
channel,
|
||||
ts,
|
||||
salt,
|
||||
uid,
|
||||
expiredTs,
|
||||
extra,
|
||||
IN_CHANNEL_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
internal class Message(
|
||||
var serviceType: Short,
|
||||
var appID: ByteArray,
|
||||
var unixTs: Int,
|
||||
var salt: Int,
|
||||
var channelName: String,
|
||||
var uid: Int,
|
||||
var expiredTs: Int,
|
||||
var extra: TreeMap<Short, String>
|
||||
) : Packable {
|
||||
override fun marshal(out: ByteBuf): ByteBuf {
|
||||
return out
|
||||
.put(serviceType)
|
||||
.put(appID)
|
||||
.put(unixTs)
|
||||
.put(salt)
|
||||
.put(channelName)
|
||||
.put(uid)
|
||||
.put(expiredTs)
|
||||
.put(extra)
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicKey5Content() : Packable {
|
||||
var serviceType: Short = 0
|
||||
var signature: String? = null
|
||||
var appID: ByteArray = byteArrayOf()
|
||||
var unixTs = 0
|
||||
var salt = 0
|
||||
var expiredTs = 0
|
||||
var extra: TreeMap<Short, String>? = null
|
||||
|
||||
constructor(
|
||||
serviceType: Short,
|
||||
signature: String?,
|
||||
appID: ByteArray,
|
||||
unixTs: Int,
|
||||
salt: Int,
|
||||
expiredTs: Int,
|
||||
extra: TreeMap<Short, String>
|
||||
) : this() {
|
||||
this.serviceType = serviceType
|
||||
this.signature = signature
|
||||
this.appID = appID
|
||||
this.unixTs = unixTs
|
||||
this.salt = salt
|
||||
this.expiredTs = expiredTs
|
||||
this.extra = extra
|
||||
}
|
||||
|
||||
override fun marshal(out: ByteBuf): ByteBuf {
|
||||
return out
|
||||
.put(serviceType)
|
||||
.put(signature!!)
|
||||
.put(appID)
|
||||
.put(unixTs)
|
||||
.put(salt)
|
||||
.put(expiredTs)
|
||||
.put(extra!!)
|
||||
}
|
||||
|
||||
fun unmarshall(input: ByteBuf) {
|
||||
serviceType = input.readShort()
|
||||
signature = input.readString()
|
||||
appID = input.readBytes()
|
||||
unixTs = input.readInt()
|
||||
salt = input.readInt()
|
||||
expiredTs = input.readInt()
|
||||
extra = input.readMap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* Created by hefeng on 15/8/10.
|
||||
* Util to generate Agora media dynamic key.
|
||||
*/
|
||||
object DynamicKeyUtil {
|
||||
@Throws(NoSuchAlgorithmException::class, InvalidKeyException::class)
|
||||
fun encodeHMAC(key: String, message: ByteArray?): ByteArray? {
|
||||
return encodeHMAC(key.toByteArray(), message)
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, InvalidKeyException::class)
|
||||
fun encodeHMAC(key: ByteArray?, message: ByteArray?): ByteArray? {
|
||||
val keySpec = SecretKeySpec(key, "HmacSHA1")
|
||||
val mac = Mac.getInstance("HmacSHA1")
|
||||
mac.init(keySpec)
|
||||
return mac.doFinal(message)
|
||||
}
|
||||
|
||||
fun bytesToHex(`in`: ByteArray): String {
|
||||
val builder = StringBuilder()
|
||||
for (b in `in`) {
|
||||
builder.append(String.format("%02x", b))
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
interface Packable {
|
||||
fun marshal(out: ByteBuf): ByteBuf
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
interface PackableEx : Packable {
|
||||
fun unmarshal(input: ByteBuf)
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class RtcTokenBuilder {
|
||||
/**
|
||||
* Builds an RTC token using an int uid.
|
||||
*
|
||||
* @param appId The App ID issued to you by Agora.
|
||||
* @param appCertificate Certificate of the application that you registered in
|
||||
* the Agora Dashboard.
|
||||
* @param channelName The unique channel name for the AgoraRTC session in the string format. The string length must be less than 64 bytes. Supported character scopes are:
|
||||
*
|
||||
* * The 26 lowercase English letters: a to z.
|
||||
* * The 26 uppercase English letters: A to Z.
|
||||
* * The 10 digits: 0 to 9.
|
||||
* * The space.
|
||||
* * "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ",".
|
||||
*
|
||||
* @param uid User ID. A 32-bit unsigned integer with a value ranging from
|
||||
* 1 to (2^32-1).
|
||||
* @param role The user role.
|
||||
*
|
||||
* * Role_Publisher = 1: RECOMMENDED. Use this role for a voice/video call or a live broadcast.
|
||||
* * Role_Subscriber = 2: ONLY use this role if your live-broadcast scenario requires authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in). In order for this role to take effect, please contact our support team to enable authentication for Hosting-in for you. Otherwise, Role_Subscriber still has the same privileges as Role_Publisher.
|
||||
*
|
||||
* @param privilegeTs Represented by the number of seconds elapsed since 1/1/1970.
|
||||
* If, for example, you want to access the Agora Service within 10 minutes
|
||||
* after the token is generated, set expireTimestamp as the current time stamp
|
||||
* + 600 (seconds).
|
||||
*/
|
||||
fun buildTokenWithUid(
|
||||
appId: String,
|
||||
appCertificate: String,
|
||||
channelName: String,
|
||||
uid: Int,
|
||||
privilegeTs: Int
|
||||
): String {
|
||||
val account = if (uid == 0) "" else uid.toString()
|
||||
return buildTokenWithUserAccount(
|
||||
appId,
|
||||
appCertificate,
|
||||
channelName,
|
||||
account,
|
||||
privilegeTs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an RTC token using a string userAccount.
|
||||
*
|
||||
* @param appId The App ID issued to you by Agora.
|
||||
* @param appCertificate Certificate of the application that you registered in
|
||||
* the Agora Dashboard.
|
||||
* @param channelName The unique channel name for the AgoraRTC session in the string format. The string length must be less than 64 bytes. Supported character scopes are:
|
||||
*
|
||||
* * The 26 lowercase English letters: a to z.
|
||||
* * The 26 uppercase English letters: A to Z.
|
||||
* * The 10 digits: 0 to 9.
|
||||
* * The space.
|
||||
* * "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ",".
|
||||
*
|
||||
* @param account The user account.
|
||||
* @param role The user role.
|
||||
*
|
||||
* * Role_Publisher = 1: RECOMMENDED. Use this role for a voice/video call or a live broadcast.
|
||||
* * Role_Subscriber = 2: ONLY use this role if your live-broadcast scenario requires authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in). In order for this role to take effect, please contact our support team to enable authentication for Hosting-in for you. Otherwise, Role_Subscriber still has the same privileges as Role_Publisher.
|
||||
*
|
||||
* @param privilegeTs represented by the number of seconds elapsed since 1/1/1970.
|
||||
* If, for example, you want to access the Agora Service within 10 minutes
|
||||
* after the token is generated, set expireTimestamp as the current time stamp
|
||||
* + 600 (seconds).
|
||||
*/
|
||||
fun buildTokenWithUserAccount(
|
||||
appId: String,
|
||||
appCertificate: String,
|
||||
channelName: String,
|
||||
account: String,
|
||||
privilegeTs: Int
|
||||
): String {
|
||||
// Assign appropriate access privileges to each role.
|
||||
val builder = AccessToken(appId, appCertificate, channelName, account)
|
||||
builder.addPrivilege(AccessToken.Privileges.JoinChannel, privilegeTs)
|
||||
builder.addPrivilege(AccessToken.Privileges.PublishAudioStream, privilegeTs)
|
||||
builder.addPrivilege(AccessToken.Privileges.PublishVideoStream, privilegeTs)
|
||||
builder.addPrivilege(AccessToken.Privileges.PublishDataStream, privilegeTs)
|
||||
|
||||
return try {
|
||||
builder.build()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import kr.co.vividnext.sodalive.agora.AccessToken.Privileges
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class RtmTokenBuilder {
|
||||
|
||||
lateinit var mTokenCreator: AccessToken
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun buildToken(
|
||||
appId: String,
|
||||
appCertificate: String,
|
||||
uid: String,
|
||||
privilegeTs: Int
|
||||
): String {
|
||||
mTokenCreator = AccessToken(appId, appCertificate, uid, "")
|
||||
mTokenCreator.addPrivilege(Privileges.RtmLogin, privilegeTs)
|
||||
return mTokenCreator.build()
|
||||
}
|
||||
|
||||
fun setPrivilege(privilege: Privileges?, expireTs: Int) {
|
||||
mTokenCreator.addPrivilege(privilege!!, expireTs)
|
||||
}
|
||||
|
||||
fun initTokenBuilder(originToken: String?): Boolean {
|
||||
mTokenCreator.fromString(originToken!!)
|
||||
return true
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user