이번 게시글에서는 1~5편까지 진행하시면서 토큰발급에 대한 어려움이 있으신 분들을 위한 마무리 게시글입니다.
public class AccessToken {
public enum Privileges {
kJoinChannel(1),
kPublishAudioStream(2),
kPublishVideoStream(3),
kPublishDataStream(4),
// For RTM only
kRtmLogin(1000);
public short intValue;
Privileges(int value) {
intValue = (short) value;
}
}
private static final String VER = "006";
public String appId;
public String appCertificate;
public String channelName;
public String uid;
public byte[] signature;
public byte[] messageRawContent;
public int crcChannelName;
public int crcUid;
public PrivilegeMessage message;
public int expireTimestamp;
public AccessToken(String appId, String appCertificate, String channelName, String uid) {
this.appId = appId;
this.appCertificate = appCertificate;
this.channelName = channelName;
this.uid = uid;
this.crcChannelName = 0;
this.crcUid = 0;
this.message = new PrivilegeMessage();
}
public String build() throws Exception {
if (! Utils.isUUID(appId)) {
return "";
}
if (!Utils.isUUID(appCertificate)) {
return "";
}
messageRawContent = Utils.pack(message);
signature = generateSignature(appCertificate,
appId, channelName, uid, messageRawContent);
crcChannelName = Utils.crc32(channelName);
crcUid = Utils.crc32(uid);
PackContent packContent = new PackContent(signature, crcChannelName, crcUid, messageRawContent);
byte[] content = Utils.pack(packContent);
return getVersion() + this.appId + Utils.base64Encode(content);
}
public void addPrivilege(Privileges privilege, int expireTimestamp) {
message.messages.put(privilege.intValue, expireTimestamp);
}
public static String getVersion() {
return VER;
}
public static byte[] generateSignature(String appCertificate,
String appID, String channelName, String uid, byte[] message) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
baos.write(appID.getBytes());
baos.write(channelName.getBytes());
baos.write(uid.getBytes());
baos.write(message);
} catch (IOException e) {
e.printStackTrace();
}
return Utils.hmacSign(appCertificate, baos.toByteArray());
}
public boolean fromString(String token) {
if (!getVersion().equals(token.substring(0, Utils.VERSION_LENGTH))) {
return false;
}
try {
appId = token.substring(Utils.VERSION_LENGTH, Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH);
PackContent packContent = new PackContent();
Utils.unpack(Utils.base64Decode(token.substring(Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH, token.length())), packContent);
signature = packContent.signature;
crcChannelName = packContent.crcChannelName;
crcUid = packContent.crcUid;
messageRawContent = packContent.rawMessage;
Utils.unpack(messageRawContent, message);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
public class PrivilegeMessage implements PackableEx {
public int salt;
public int ts;
public TreeMap<Short, Integer> messages;
public PrivilegeMessage() {
salt = Utils.randomInt();
ts = Utils.getTimestamp() + 24 * 3600;
messages = new TreeMap<>();
}
@Override
public ByteBuf marshal(ByteBuf out) {
return out.put(salt).put(ts).putIntMap(messages);
}
@Override
public void unmarshal(ByteBuf in) {
salt = in.readInt();
ts = in.readInt();
messages = in.readIntMap();
}
}
public class PackContent implements PackableEx {
public byte[] signature;
public int crcChannelName;
public int crcUid;
public byte[] rawMessage;
public PackContent() {
// Nothing done
}
public PackContent(byte[] signature, int crcChannelName, int crcUid, byte[] rawMessage) {
this.signature = signature;
this.crcChannelName = crcChannelName;
this.crcUid = crcUid;
this.rawMessage = rawMessage;
}
@Override
public ByteBuf marshal(ByteBuf out) {
return out.put(signature).put(crcChannelName).put(crcUid).put(rawMessage);
}
@Override
public void unmarshal(ByteBuf in) {
signature = in.readBytes();
crcChannelName = in.readInt();
crcUid = in.readInt();
rawMessage = in.readBytes();
}
}
}
----------------------------------------------------------------------------------------------------------------------
public class ByteBuf {
ByteBuffer buffer = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN);
public ByteBuf() {
}
public ByteBuf(byte[] bytes) {
this.buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
}
public byte[] asBytes() {
byte[] out = new byte[buffer.position()];
buffer.rewind();
buffer.get(out, 0, out.length);
return out;
}
// packUint16
public ByteBuf put(short v) {
buffer.putShort(v);
return this;
}
public ByteBuf put(byte[] v) {
put((short)v.length);
buffer.put(v);
return this;
}
// packUint32
public ByteBuf put(int v) {
buffer.putInt(v);
return this;
}
public ByteBuf put(long v) {
buffer.putLong(v);
return this;
}
public ByteBuf put(String v) {
return put(v.getBytes());
}
public ByteBuf put(TreeMap<Short, String> extra) {
put((short)extra.size());
for (Map.Entry<Short, String> pair : extra.entrySet()) {
put(pair.getKey());
put(pair.getValue());
}
return this;
}
public ByteBuf putIntMap(TreeMap<Short, Integer> extra) {
put((short)extra.size());
for (Map.Entry<Short, Integer> pair : extra.entrySet()) {
put(pair.getKey());
put(pair.getValue());
}
return this;
}
public short readShort() {
return buffer.getShort();
}
public int readInt() {
return buffer.getInt();
}
public byte[] readBytes() {
short length = readShort();
byte[] bytes = new byte[length];
buffer.get(bytes);
return bytes;
}
public String readString() {
byte[] bytes = readBytes();
return new String(bytes);
}
public TreeMap readMap() {
TreeMap<Short, String> map = new TreeMap<>();
short length = readShort();
for (short i = 0; i < length; ++i) {
short k = readShort();
String v = readString();
map.put(k, v);
}
return map;
}
public TreeMap<Short, Integer> readIntMap() {
TreeMap<Short, Integer> map = new TreeMap<>();
short length = readShort();
for (short i = 0; i < length; ++i) {
short k = readShort();
Integer v = readInt();
map.put(k, v);
}
return map;
}
}
----------------------------------------------------------------------------------------------------------------------
public interface Packable {
ByteBuf marshal(ByteBuf out);
}
----------------------------------------------------------------------------------------------------------------------
public interface PackableEx extends Packable {
void unmarshal(ByteBuf in);
}
----------------------------------------------------------------------------------------------------------------------
public class PrefManager {
public static SharedPreferences getPreferences(Context context) {
return context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
}
}
public class RtcTokenBuilder {
public enum Role {
/**
* DEPRECATED. Role_Attendee has the same privileges as Role_Publisher.
*/
Role_Attendee(0),
/**
* RECOMMENDED. Use this role for a voice/video call or a live broadcast, if your scenario does not require authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in).
*/
Role_Publisher(1),
/**
* Only use this role if your scenario require authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in).
*
* @note 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.
*/
Role_Subscriber(2),
/**
* DEPRECATED. Role_Attendee has the same privileges as Role_Publisher.
*/
Role_Admin(101);
public int initValue;
Role(int initValue) {
this.initValue = initValue;
}
}
/**
* 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:
* <ul>
* <li> The 26 lowercase English letters: a to z.</li>
* <li> The 26 uppercase English letters: A to Z.</li>
* <li> The 10 digits: 0 to 9.</li>
* <li> The space.</li>
* <li> "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ",".
* </ul>
* @param uid User ID. A 32-bit unsigned integer with a value ranging from
* 1 to (2^32-1).
* @param role The user role.
* <ul>
* <li> Role_Publisher = 1: RECOMMENDED. Use this role for a voice/video call or a live broadcast.</li>
* <li> 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.</li>
* </ul>
* @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).
*/
public String buildTokenWithUid(String appId, String appCertificate, String channelName, int uid, Role role, int privilegeTs) {
String account = uid == 0 ? "" : String.valueOf(uid);
return buildTokenWithUserAccount(appId, appCertificate, channelName,
account, role, 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:
* <ul>
* <li> The 26 lowercase English letters: a to z.</li>
* <li> The 26 uppercase English letters: A to Z.</li>
* <li> The 10 digits: 0 to 9.</li>
* <li> The space.</li>
* <li> "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ",".
* </ul>
* @param account The user account.
* @param role The user role.
* <ul>
* <li> Role_Publisher = 1: RECOMMENDED. Use this role for a voice/video call or a live broadcast.</li>
* <li> 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.</li>
* </ul>
* @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).
*/
public String buildTokenWithUserAccount(String appId, String appCertificate,
String channelName, String account, Role role, int privilegeTs) {
// Assign appropriate access privileges to each role.
AccessToken builder = new AccessToken(appId, appCertificate, channelName, account);
builder.addPrivilege(AccessToken.Privileges.kJoinChannel, privilegeTs);
if (role == Role.Role_Publisher || role == Role.Role_Subscriber || role == Role.Role_Admin) {
builder.addPrivilege(AccessToken.Privileges.kPublishAudioStream, privilegeTs);
builder.addPrivilege(AccessToken.Privileges.kPublishVideoStream, privilegeTs);
builder.addPrivilege(AccessToken.Privileges.kPublishDataStream, privilegeTs);
}
try {
return builder.build();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
----------------------------------------------------------------------------------------------------------------------
public class RtmTokenBuilder {
public enum Role {
Rtm_User(1);
int value;
Role(int value) {
this.value = value;
}
}
public AccessToken mTokenCreator;
public String buildToken(String appId, String appCertificate,
String uid, Role role, int privilegeTs) throws Exception {
mTokenCreator = new AccessToken(appId, appCertificate, uid, "");
mTokenCreator.addPrivilege(AccessToken.Privileges.kRtmLogin, privilegeTs);
return mTokenCreator.build();
}
public void setPrivilege(AccessToken.Privileges privilege, int expireTs) {
mTokenCreator.addPrivilege(privilege, expireTs);
}
public boolean initTokenBuilder(String originToken) {
mTokenCreator.fromString(originToken);
return true;
}
}
----------------------------------------------------------------------------------------------------------------------
public class Utils {
public static final long HMAC_SHA256_LENGTH = 32;
public static final int VERSION_LENGTH = 3;
public static final int APP_ID_LENGTH = 32;
public static byte[] hmacSign(String keyString, byte[] msg) throws InvalidKeyException, NoSuchAlgorithmException {
SecretKeySpec keySpec = new SecretKeySpec(keyString.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
return mac.doFinal(msg);
}
public static byte[] pack(PackableEx packableEx) {
ByteBuf buffer = new ByteBuf();
packableEx.marshal(buffer);
return buffer.asBytes();
}
public static void unpack(byte[] data, PackableEx packableEx) {
ByteBuf buffer = new ByteBuf(data);
packableEx.unmarshal(buffer);
}
public static String base64Encode(byte[] data) {
byte[] encodedBytes = Base64.encodeBase64(data);
return new String(encodedBytes);
}
public static byte[] base64Decode(String data) {
return Base64.decodeBase64(data.getBytes());
}
public static int crc32(String data) {
// get bytes from string
byte[] bytes = data.getBytes();
return crc32(bytes);
}
public static int crc32(byte[] bytes) {
CRC32 checksum = new CRC32();
checksum.update(bytes);
return (int)checksum.getValue();
}
public static int getTimestamp() {
return (int)((new Date().getTime())/1000);
}
public static int randomInt() {
return new SecureRandom().nextInt();
}
public static boolean isUUID(String uuid) {
if (uuid.length() != 32) {
return false;
}
return uuid.matches("\\p{XDigit}+");
}
}
----------------------------------------------------------------------------------------------------------------------
영상처리 토큰은 RtcBaseActivity의 joinChannel 함수를 참고하시면 되구요.
채팅처리 토큰은 RoleActivity의 getRtmToken을 참고하시면 됩니다.
이걸로 라이브관련 포스팅은 끝입니다.
궁금하신 사항은 댓글 또는 공지사항의 오픈채팅방으로 와주세요.
ㄱ ㅏ ㄱ 푸시
ㅗ o ㅗ 푸시 :D
'Android' 카테고리의 다른 글
Android TextView 가운데 줄 긋기 (취소선) (0) | 2022.06.14 |
---|---|
Android intent(인텐트)로 설정화면 호출하기 (0) | 2022.06.09 |
아고라 플랫폼을 이용한 안드로이드 라이브스트리밍(RTSP) - 5 (2) | 2022.05.13 |
아고라 플랫폼을 이용한 안드로이드 라이브스트리밍(RTSP) - 4 (0) | 2022.05.12 |
아고라 플랫폼을 이용한 안드로이드 라이브스트리밍(RTSP) - 3 (0) | 2022.05.12 |
댓글