Android整合騰訊直播(無需後臺配合一小時讓你擁有直播APP)
直播互動的功能,最終選擇了騰訊雲平臺進行開發,LiveRoom元件裡面包含了(直播,連麥,彈幕,私信等)功能,我們需要的是推流,拉流都交個騰訊雲來處理,騰訊雲這方面功能也是比較齊全的了(粗略大概2分鐘可看完本文,能像我寫如此詳細的全網暫時木有可以說,如果你是第一次接觸直播可謂福音不敢說,無需自己後臺一小時讓你擁有直播app不在話下!文末有巨型福利相送及手寫FFmpeg推流拉流教程原始碼+高清視訊)。
而且騰訊雲直播也是按照流量收費的,沒有其他任何費用,所以如果要使用騰訊雲直播+IM聊天(基本免費按日活收費)功能的,最好還是全部選用騰訊一家即可。
網上相關的文章很少,而且大廠的文件雖然高階但看起來整合過程還是有點慢的,
我這篇文章主要是手把手詳細介紹Android 快速整合騰訊移動直播,無需後臺任何配合我們前端直接除錯成功!看完本文大概十分鐘,半天搞定整個直播互動連麥。整合路上大概可以幫助大家節省一天時間,希望可以幫助到各位.
下載SDK
1.功能不同多種版本可以選擇,一般來說,選擇全功能專業版就好了.
官網整合到AS步驟:https://cloud.tencent.com/document/product/454/7877
個人用你 qq號登入後即可下載,
另外開通直播雲服務:https://cloud.tencent.com/document/product/267/13551
以及IM雲通訊功能:https://console.cloud.tencent.com/avc 建立應用,點選購買開通一元基礎版即可滿足除錯。
下載SDK後首先,執行Demo原始碼,跑通原始碼直接看到效果是最好的。建議先在demo原始碼上改動來實現除錯成功
執行Domo專案後你可以試試可以線上看到同樣使用本SDK demo 的使用者在上面除錯,你只需要把專案裡的sdkAppId 和直播雲appId 替換到你自己的專案,就可以替換成你再騰訊雲開通的直播服務和使用你剛剛建立的專案的Im聊天連麥功能了。但是這裡面還是有一些步驟的,不是簡單的替換就可以的。
首先我建議你用debug的方式,在點選美女直播,到點選直播列表,到進入直播聊天室,以及新建直播間,這一部分多打一些斷點,你就大概知道帶直播連麥的直播聊天功能大致流程是:登入聊天室–獲取推流地址–直播。 這裡的登入是必須的步驟,直播和雲通訊是互通的,你可以用這一行程式碼來檢測是否登入雲通訊成功:String loginUser = TIMManager.getInstance().getLoginUser();有返回值說明已經IMSDk初始化登入成功了。
如果你仔細想先用demo測試下 推流 拉流是否成功,那麼可以先配置好播放域名,然後利用demo裡的 首頁—除錯工具—RTMP推流,將你的在騰訊雲直播生成推流地址出複製–貼上到輸入框–點選底部開始按鈕–檢視是否推流成功–log,然後電腦下載VLC播放器,開啟—網路流播放—將的在騰訊雲直播生成播放地址貼上—播放,即可測試是否推流,拉流成功。具體見下面步驟:
播放域名設定:https://console.cloud.tencent.com/live/domainmanage
如下圖,一定是綠色標記顯示了,才說明你的域名設定成功並cname成功了,否則是不會播放成功的,這裡要是你自己的備案的域名,如果你沒有可以阿里雲購買,然後申請備案,可能需要你購買一個雲伺服器才會送備案號,有了域名後解析是需要注意,如果你的是域名比如是abc.cn 阿里雲的解析設定裡 記錄型別請填@,記錄值親填騰訊後臺記錄值那裡給你自動生成的那一串全部。如果你的是www.abc.cn 那麼記錄型別填www 否則是不會成功的哦。配置後可以ping下是能看到騰訊的伺服器地址就算配置ok了。但是一定時要下圖中的綠色標記顯示了才算完全正常哦!
推流,拉流地址生成:https://console.cloud.tencent.com/live/livecodemanage
推流域名可以先不設定自己的,預設已經給你生成了。至此你如果你不需要互動連麥的功能,只需要用騰訊的點播播放器就可以直接播放你生成的播放觀看實時直播了。
後臺對接騰訊雲
我們推流,拉流都是交給騰訊雲處理的,
LiveRoom 為什麼需要 login?
LiveRoom 單靠一個終端的元件無法獨自執行,它依賴一個後臺服務為其實現房間管理和狀態協調,這個後臺服務我們稱之為房間服務(RoomService)。而要使用這個房間服務,LiveRoom 就需要先進行登入(login)。
login 有很多引數需要填寫,我應當如何填寫這些引數呢?
如下表格中列舉了三種填寫方案,每種方案都有其適用場景:方案一適合除錯;方案二適合快速上線;方案三適合自行定製;
你可有看下圖:
https://cloud.tencent.com/document/product/454/14606
login(serverDomain, sdkAppID, accType, userID, userSig),可以看到登入索要的引數,accountType 是雲通訊後臺你建立專案IM 配置那裡可以生成的,userId是你的真實專案你的使用者userId使用者唯一標識即可,只有userSig 是需要後臺生成的,但是這個我們除錯階段可以在客戶端生成方便是一樣的,
你也可以直接複製下面的生成userSig ,
package com.tencent.liteav.demo.webrtc;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.zip.Deflater;
public class WebRTCSigApi {
private int mSdkAppid = 0;
private PrivateKey mPrivateKey = null;
private PublicKey mPublicKey = null;
/**
* 設定sdkappid
* @param sdkappid
*/
public void setSdkAppid(int sdkappid) {
this.mSdkAppid = sdkappid;
}
/**
* 設定私鑰 如果要生成userSig和privateMapKey則需要私鑰
* @param privateKey 私鑰檔案內容
*/
public void setPrivateKey(String privateKey) {
String privateKeyPEM = privateKey.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\r\n", "");
char[] chars = privateKeyPEM.toCharArray();
byte[] encodedKey = Base64.decode(chars);//Base64.getDecoder().decode(privateKeyPEM.getBytes());
try {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
this.mPrivateKey = keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 設定公鑰 如果要驗證userSig和privateMapKey則需要公鑰
* @param publicKey 公鑰檔案內容
*/
public void setPublicKey(String publicKey) {
String publicKeyPEM = publicKey.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "");
byte[] encodedKey = Base64.decode(publicKeyPEM.toCharArray());//Base64.getDecoder().decode(publicKeyPEM.getBytes());
try {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
this.mPublicKey = keyFactory.generatePublic(keySpec);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* ECDSA-SHA256簽名
* @param data 需要簽名的資料
* @return 簽名
*/
public byte[] sign(byte[] data) {
try {
Signature signer = Signature.getInstance("SHA256withECDSA");
signer.initSign(this.mPrivateKey);
signer.update(data);
return signer.sign();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 驗證ECDSA-SHA256簽名
* @param data 需要驗證的資料原文
* @param sig 需要驗證的簽名
* @return true:驗證成功 false:驗證失敗
*/
public boolean verify(byte[] data, byte[] sig) {
try {
Signature signer = Signature.getInstance("SHA256withECDSA");
signer.initVerify(this.mPublicKey);
signer.update(data);
return signer.verify(sig);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 用於url的base64encode
* '+' => '*', '/' => '-', '=' => '_'
* @param data 需要編碼的資料
* @return 編碼後的base64資料
*/
private byte[] base64UrlEncode(byte[] data) {
char[] encode1 = Base64.encode(data);
byte[] encode =new String(encode1).getBytes();
// byte[] encode = Base64.encode(data);//Base64.getEncoder().encode(data);
for (int i = 0; i < encode.length; i++) {
if (encode[i] == '+') {
encode[i] = '*';
} else if (encode[i] == '/') {
encode[i] = '-';
} else if (encode[i] == '=') {
encode[i] = '_';
}
}
return encode;
}
/**
* 用於url的base64decode
* '*' => '+', '-' => '/', '_' => '='
* @param data 需要解碼的資料
* @return 解碼後的資料
*/
private byte[] base64UrlDecode(byte[] data) {
byte[] encode = Arrays.copyOf(data, data.length);
for (int i = 0; i < encode.length; i++) {
if (encode[i] == '*') {
encode[i] = '+';
} else if (encode[i] == '-') {
encode[i] = '/';
} else if (encode[i] == '_') {
encode[i] = '=';
}
}
return encode;
}
/**
* 生成userSig
* @param userid 使用者名稱
* @param expire userSig有效期,出於安全考慮建議為300秒,您可以根據您的業務場景設定其他值。
* @return 生成的userSig
*/
public String genUserSig(String userid, int expire) {
String time = String.valueOf(System.currentTimeMillis()/1000);
String serialString =
"TLS.appid_at_3rd:" + 0 + "\n" +
"TLS.account_type:" + 0 + "\n" +
"TLS.identifier:" + userid + "\n" +
"TLS.sdk_appid:" + this.mSdkAppid + "\n" +
"TLS.time:" + time + "\n" +
"TLS.expire_after:" + expire +"\n";
byte[] signBytes = sign(serialString.getBytes(Charset.forName("UTF-8")));
char[] encode = Base64.encode(signBytes);
String sig = new String(encode);
// String sig = Base64.encode(signBytes);//Base64.getEncoder().encodeToString(signBytes);
String jsonString = "{"
+ "\"TLS.account_type\":\"" + 0 +"\","
+"\"TLS.identifier\":\"" + userid +"\","
+"\"TLS.appid_at_3rd\":\"" + 0 +"\","
+"\"TLS.sdk_appid\":\"" + this.mSdkAppid +"\","
+"\"TLS.expire_after\":\"" + expire +"\","
+"\"TLS.sig\":\"" + sig +"\","
+"\"TLS.time\":\"" + time +"\","
+"\"TLS.version\": \"201512300000\""
+"}";
//compression
Deflater compresser = new Deflater();
compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8")));
compresser.finish();
byte [] compressBytes = new byte [512];
int compressBytesLength = compresser.deflate(compressBytes);
compresser.end();
String userSig = new String(base64UrlEncode(Arrays.copyOfRange(compressBytes, 0, compressBytesLength)));
return userSig;
}
/**
* 生成privateMapKey
* @param userid 使用者名稱
* @param roomid 房間號
* @param expire privateMapKey有效期,出於安全考慮建議為300秒,您可以根據您的業務場景設定其他值。
* @return 生成的privateMapKey
*/
public String genPrivateMapKey(String userid, int roomid, int expire) {
String time = String.valueOf(System.currentTimeMillis()/1000);
//視訊校驗位需要用到的欄位
/*
cVer unsigned char/1 版本號,填0
wAccountLen unsigned short /2 第三方自己的帳號長度
buffAccount wAccountLen 第三方自己的帳號字元
dwSdkAppid unsigned int/4 sdkappid
dwRoomId unsigned int/4 群組號碼
dwExpTime unsigned int/4 過期時間 (當前時間 + 有效期(單位:秒,建議300秒))
dwPrivilegeMap unsigned int/4 許可權位
dwAccountType unsigned int/4 第三方帳號型別
*/
int accountLength = userid.length();
int offset = 0;
byte[] bytes = new byte[1+2+accountLength+4+4+4+4+4];
//cVer
bytes[offset++] = 0;
//wAccountLen
bytes[offset++] = (byte)((accountLength & 0xFF00) >> 8);
bytes[offset++] = (byte)(accountLength & 0x00FF);
//buffAccount
for (; offset < 3 + accountLength; ++offset) {
bytes[offset] = (byte)userid.charAt(offset - 3);
}
//dwSdkAppid
bytes[offset++] = (byte)((this.mSdkAppid & 0xFF000000) >> 24);
bytes[offset++] = (byte)((this.mSdkAppid & 0x00FF0000) >> 16);
bytes[offset++] = (byte)((this.mSdkAppid & 0x0000FF00) >> 8);
bytes[offset++] = (byte)(this.mSdkAppid & 0x000000FF);
//dwAuthId
long nRoomId = Long.valueOf(roomid);
bytes[offset++] = (byte)((nRoomId & 0xFF000000) >> 24);
bytes[offset++] = (byte)((nRoomId & 0x00FF0000) >> 16);
bytes[offset++] = (byte)((nRoomId & 0x0000FF00) >> 8);
bytes[offset++] = (byte)(nRoomId & 0x000000FF);
//dwExpTime
long expiredTime = Long.valueOf(time) + expire;
bytes[offset++] = (byte)((expiredTime & 0xFF000000) >> 24);
bytes[offset++] = (byte)((expiredTime & 0x00FF0000) >> 16);
bytes[offset++] = (byte)((expiredTime & 0x0000FF00) >> 8);
bytes[offset++] = (byte)(expiredTime & 0x000000FF);
//dwPrivilegeMap
bytes[offset++] = (byte)((255 & 0xFF000000) >> 24);
bytes[offset++] = (byte)((255 & 0x00FF0000) >> 16);
bytes[offset++] = (byte)((255 & 0x0000FF00) >> 8);
bytes[offset++] = (byte)(255 & 0x000000FF);
//dwAccountType
bytes[offset++] = (byte)((0 & 0xFF000000) >> 24);
bytes[offset++] = (byte)((0 & 0x00FF0000) >> 16);
bytes[offset++] = (byte)((0 & 0x0000FF00) >> 8);
bytes[offset++] = (byte)(0 & 0x000000FF);
char[] encode = Base64.encode(bytes);
String userbuf = new String(encode);
// String userbuf = Base64.getEncoder().encodeToString(bytes);//Base64.getEncoder().encodeToString(bytes);
String serialString =
"TLS.appid_at_3rd:" + 0 + "\n" +
"TLS.account_type:" + 0 + "\n" +
"TLS.identifier:" + userid + "\n" +
"TLS.sdk_appid:" + this.mSdkAppid + "\n" +
"TLS.time:" + time + "\n" +
"TLS.expire_after:" + expire +"\n" +
"TLS.userbuf:" + userbuf + "\n";
byte[] signBytes = sign(serialString.getBytes(Charset.forName("UTF-8")));
char[] encodes = Base64.encode(signBytes);
String sig = new String(encodes);
// String sig = Base64.getEncoder().encodeToString(signBytes);//Base64.getEncoder().encodeToString(signBytes);
String jsonString = "{"
+"\"TLS.appid_at_3rd\":\"" + 0 +"\","
+"\"TLS.account_type\":\"" + 0 +"\","
+"\"TLS.identifier\":\"" + userid +"\","
+"\"TLS.sdk_appid\":\"" + this.mSdkAppid +"\","
+"\"TLS.expire_after\":\"" + expire +"\","
+"\"TLS.sig\":\"" + sig +"\","
+"\"TLS.time\":\"" + time +"\","
+"\"TLS.userbuf\":\"" + userbuf +"\","
+"\"TLS.version\": \"201512300000\""
+"}";
//compression
Deflater compresser = new Deflater();
compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8")));
compresser.finish();
byte [] compressBytes = new byte [512];
int compressBytesLength = compresser.deflate(compressBytes);
compresser.end();
String privateMapKey = new String(base64UrlEncode(Arrays.copyOfRange(compressBytes, 0, compressBytesLength)));
return privateMapKey;
}
/* public static void main(String[] args) {
int sdkappid = 140xxxxx; //騰訊云云通訊你建立的sdkappid
int roomid = 1234; //音視訊房間號先隨便填除錯roomid
String userid = "webrtc98"; //使用者名稱userid 先隨便填除錯
File privateKeyFile = new File("private_key");
byte[] privateKey = new byte[(int)privateKeyFile.length()];
File publicKeyFile = new File("public_key");
byte[] publicKey = new byte[(int)publicKeyFile.length()];
try {
//讀取私鑰的內容
//PS:不要把私鑰檔案暴露到外網直接下載了哦
FileInputStream in1 = new FileInputStream(privateKeyFile);
in1.read(privateKey);
in1.close();
//讀取公鑰的內容
FileInputStream in2 = new FileInputStream(publicKeyFile);
in2.read(publicKey);
in2.close();
} catch (Exception e ) {
e.printStackTrace();
}
WebRTCSigApi api = new WebRTCSigApi();
api.setSdkAppid(sdkappid);
api.setPrivateKey(new String(privateKey));
api.setPublicKey(new String(publicKey));
//生成userSig
String userSig = api.genUserSig(userid, 300);
//生成privateMapKey
String privateMapKey = api.genPrivateMapKey(userid, roomid, 300);
System.out.println("userSig:\n" + userSig);
System.out.println("privateMapKey:\n" + privateMapKey);
}*/
}
base64類
/*
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
*/
package com.tencent.liteav.demo.webrtc;
/**
* Created by wangtianfei01 on 17/4/6.
*/
public class Base64 {
private static char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
.toCharArray();
private static byte[] codes = new byte[256];
public static char[] encode(byte[] data) {
char[] out = new char[((data.length + 2) / 3) * 4];
for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
boolean quad = false;
boolean trip = false;
int val = (0xFF & (int) data[i]);
val <<= 8;
if ((i + 1) < data.length) {
val |= (0xFF & (int) data[i + 1]);
trip = true;
}
val <<= 8;
if ((i + 2) < data.length) {
val |= (0xFF & (int) data[i + 2]);
quad = true;
}
out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
val >>= 6;
out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
val >>= 6;
out[index + 1] = alphabet[val & 0x3F];
val >>= 6;
out[index + 0] = alphabet[val & 0x3F];
}
return out;
}
public static byte[] decode(char[] data) {
int len = ((data.length + 3) / 4) * 3;
if (data.length > 0 && data[data.length - 1] == '=') {
--len;
}
if (data.length > 1 && data[data.length - 2] == '=') {
--len;
}
byte[] out = new byte[len];
int shift = 0;
int accum = 0;
int index = 0;
for (int ix = 0; ix < data.length; ix++) {
int value = codes[data[ix] & 0xFF];
if (value >= 0) {
accum <<= 6;
shift += 6;
accum |= value;
if (shift >= 8) {
shift -= 8;
out[index++] = (byte) ((accum >> shift) & 0xff);
}
}
}
if (index != out.length) {
throw new Error("miscalculated data length!");
}
return out;
}
static {
for (int i = 0; i < 256; i++) {
codes[i] = -1;
}
for (int i = 'A'; i <= 'Z'; i++) {
codes[i] = (byte) (i - 'A');
}
for (int i = 'a'; i <= 'z'; i++) {
codes[i] = (byte) (26 + i - 'a');
}
for (int i = '0'; i <= '9'; i++) {
codes[i] = (byte) (52 + i - '0');
}
codes['+'] = 62;
codes['/'] = 63;
}
}
生成:
private void ininsig() {
int sdkappid = 1400162038; //騰訊云云通訊sdkappid 1400162038
int roomid = 1234; //音視訊房間號roomid
String userid = "webrtc98777"; //使用者名稱userid
/* File privateKeyFile = new File("private_key");
byte[] privateKey = new byte[(int)privateKeyFile.length()];
File publicKeyFile = new File("public_key");
byte[] publicKey = new byte[(int)publicKeyFile.length()];
try {
//讀取私鑰的內容
//PS:不要把私鑰檔案暴露到外網直接下載了哦
FileInputStream in1 = new FileInputStream(privateKeyFile);
in1.read(privateKey);
in1.close();
//讀取公鑰的內容
FileInputStream in2 = new FileInputStream(publicKeyFile);
in2.read(publicKey);
in2.close();
} catch (Exception e ) {
e.printStackTrace();
}*/
WebRTCSigApi api = new WebRTCSigApi();
api.setSdkAppid(sdkappid);
// api.setPrivateKey(new String(privateKey));
api.setPrivateKey("-----BEGIN PRIVATE KEY-----\r\n"+"你的IM建立專案設定裡下載獲取的私鑰MBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+eeR7R2n0cTcwmHj\r\n" + "你的IM建立專案設定裡下載獲取的私鑰slopZXQycM/RznRQ/TjvaATNmbKcj\r\n" + "你的IM建立專案設定裡下載獲取的私鑰mSNbq718jkTX\r\n"+"-----END PRIVATE KEY-----\r\n");
// api.setPublicKey(new String(publicKey));
//生成userSig
String userSig = api.genUserSig(userid, 2000*50);
SharedPreUtil.saveString(this,"userSig",userSig);
System.out.println("privateMapKey:\n" + userSig);
Toast.makeText(this, "sig:"+userSig, Toast.LENGTH_SHORT).show();
}
建議你也可以自行閱讀文件獲取,因為這裡面介紹瞭如何獲取accountType 以及公鑰私鑰下載(後面用nodejs命令列註冊騰訊雲需要用到的,主要是在你下載的RoomTool 工具包裡面的config.js檔案修改):https://cloud.tencent.com/document/product/454/14548
點選 RoomTool.zip 下載騰訊雲 RoomService 後臺配置工具,這是一個基於 Node.js 的配置工具,需要您在使用前 安裝 Node.js 。配置工具壓縮包中包含的 pdf 和 PPT 有詳細的配置說明,這裡僅簡要概括一下各個配置項的含義和作用。
修改config.js資料夾裡面的請完全按照它的示例哦,不要以為那些提示是不用的了哈,如下圖保留複製貼上你的即可,
另外就是在生成userSig的時候,也是替換\t\r,文件裡少了一個,坑死人了 會報一個錯 ,注意哦。
config.js檔案按照提示修改完畢後,按照nodejs環境後,註冊騰訊雲:開啟命令列 cmd --,然後 執行:node setConfigInfo.js 1
根據提示執行後會顯示請求成功的返回提示哦,如果你的私鑰配置錯誤,會提示相應的錯誤,有時候提示不準確,還有就是你的播放域名要配置才可以哦。
到這裡你如果還有什麼問題你可以提交工單會有客服處理的或論壇搜尋答案。
在除錯的時候我們可以先寫死:userId(注意如果用兩個手機測試,這裡需要改成不一樣的否則IM或別踢出的報user not logged in),
直播SDK錯誤碼對照表:https://cloud.tencent.com/document/product/454/8292
到這裡就差不多了你可以用兩個以上的手機(每個的userId都要唯一)同時線上除錯直播並連麥了哈
相關文章
- 擁有線上直播原始碼之後還需要了解什麼原始碼
- 騰訊在美直播平臺Trovo曝光
- 影視APP直播盒子原始碼 第三方介面無需採集APP原始碼
- 短影片直播APP成品開發直播系統仿抖音APP無加密APP加密
- 直播APP原始碼,直播系統推流SDK(Android)APP原始碼Android
- 直播平臺製作,Android 懸浮窗延時5秒返回APP問題AndroidAPP
- 騰訊投資入股摘星網路,後者擁有《雞你太美》等遊戲遊戲
- 對騰訊後續“組局”遊戲直播的猜想遊戲
- 直播平臺軟體開發,Android 計時器,定時功能Android
- 直播 App 原始碼搭建簡易直播平臺及個人開發直播系統的難點APP原始碼
- 直播app開發,使用者設定密碼時的後臺預設要求設定APP密碼
- 【從 0 開始開發一款直播 APP】10 騰訊雲通訊及SDK整合APP
- 美顏SDK人臉貼紙已成直播平臺剛需
- redux 時間旅行,你值得擁有!Redux
- hubilder如何入後臺java配合開發webapp?JavaWebAPP
- 短影片直播APP原生開發直播系統無加密搭建定製短影片APP加密
- 直播app開發,Android ListView好友列表展示APPAndroidView
- 使用 Agora 為Android APP新增視訊直播GoAndroidAPP
- JAVA 對接騰訊雲直播Java
- 線上教育直播點播平臺搭建網路教育直播APP帶一對一APP
- 利用Twitch直播平臺進行品牌互動營銷時需考慮的7個方面
- 直播社交平臺中美顏sdk爆火的背後有哪些心理原因?
- 搭建個人直播間,實現24小時B站、鬥魚、虎牙等無人直播!
- 直播+社交:一對一社交app原始碼或成直播平臺開發新寵APP原始碼
- 讓你的App有聲音APP
- 從一無所有,到成品直播交友APP上架APP
- 教育直播APP原生開發,成品原始碼無加密APP原始碼加密
- 短影片APP社交無加密直播定製功能需求APP加密
- 直播成今年移動APP大熱門,直播帶貨app原始碼的開發有何亮點APP原始碼
- app直播原始碼,android AES加密解密實現APP原始碼Android加密解密
- app直播原始碼,Android 設定系統亮度APP原始碼Android
- app直播商城原始碼,有哪些常用的加密方式APP原始碼加密
- app直播原始碼“助力”直播架構,走上探索之路APP原始碼架構
- app直播原始碼如何實現直播間紅包功能APP原始碼
- app直播原始碼,edusoho直播回放增加進度條APP原始碼
- 魚羊兒教您搭建手機直播APP平臺!直播系統原始碼開發!APP原始碼
- 直播APP手機收集APP
- 一小時完成後臺開發:DjangoRestFramework開發實踐DjangoRESTFramework