全部產品
Search
文件中心

ApsaraVideo Live:Android端實現語聊房

更新時間:Nov 19, 2025

本文檔將介紹如何在您的Android專案中整合 ARTC SDK, 快速實現一個簡單的純音頻互動App,適用於語音通話、語聊房等情境。

功能介紹

在開始前,您需要瞭解以下有關音視頻即時互動的基本概念:

  • ARTC SDK:阿里雲即時音視頻產品,協助開發中快速實現即時音視頻互動的SDK。

  • 頻道:房間的概念,在同一個頻道內的使用者可以進行即時互動。

  • 主播:可在頻道內發布音視頻流,並可訂閱其他主播發布的音視頻流。

  • 觀眾:可在頻道內訂閱音視頻流,不能發布音視頻流。

下圖展示了實現語音通話及語聊房的基本流程:

  1. 使用者需要先調用joinChannel加入頻道,才能進行推流、拉流:

    • 普通純語音通話情境:所有使用者都是主播角色,可以進行推流和拉流;

    • 語聊房情境:需要在頻道內推流的使用者佈建主播角色;如果使用者只需要拉流,不需要推流,則設定觀眾角色;

    • 通過setClientRole為使用者佈建不同的角色。

  2. 加入頻道後,不同角色的使用者有不同的推拉流行為:

    • 所有頻道內的使用者都可以接收相同頻道內的音視頻流;

    • 主播角色可以在頻道內推音視頻流;

    • 觀眾如果需要推流,需要調用setClientRole方法,將使用者角色切換成主播,便可以推流。

樣本專案

阿里雲ARTC SDK提供了開源的樣本專案供客戶參考,您可以前往下載或查看樣本源碼

前提條件

在實現功能以前,請確保您的開發環境滿足:

  • 開發工具:Android Studio 2020.3.1 及以上版本。

  • 測試裝置:Android 5.0(SDK API Level 21)及以上版本的測試裝置。

說明

推薦使用真機測試,類比機可能存在功能缺失。

  • 網路環境:需要穩定的網路連接。

  • 應用準備:擷取即時音視頻應用的AppID和AppKey,詳情請參見建立應用

  • 建立專案和配置:已建立專案並為專案添加了音頻、網路等音視頻互動的相關許可權,此外需要整合 ARTC SDK,相關步驟請參考Android端實現音視訊通話

實現步驟

下面將以語聊房情境為例進行示範,相關功能時序如下:

語聊房情境主要特點如下:

  • 純音頻:頻道內僅包含音頻,不包含視頻。

  • 主播/觀眾角色:頻道內角色分為主播和觀眾角色,主播角色可以推拉音頻流,觀眾角色只能拉取主播推送的音頻流;觀眾角色可以切換為主播角色。

實現純音頻互動

1、申請許可權請求

進入音視訊通話時,檢查是否已在App中授予了所需要的許可權:

private static final int REQUEST_PERMISSION_CODE = 101;

private static final String[] PERMISSION_MANIFEST = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_PHONE_STATE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.CAMERA
};

private static final String[] PERMISSION_MANIFEST33 = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_PHONE_STATE,
    Manifest.permission.CAMERA
};

private static String[] getPermissions() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
        return PERMISSION_MANIFEST;
    }
    return PERMISSION_MANIFEST33;
}

public boolean checkOrRequestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(getPermissions(), REQUEST_PERMISSION_CODE);
            return false;
        }
    }
    return true;
}

2、鑒權Token

加入ARTC頻道需要一個鑒權Token,用於鑒權使用者的合法身份,其鑒權Token建置規則詳情請參見:Token鑒權。Token 產生有兩種方式:單參數方式和多參數方式,不同的Token產生方式需要調用SDK不同的加入頻道(joinChannel)的介面。

上線發布階段

由於Token的產生需要使用AppKey,寫死在用戶端存在泄露的風險,因此強烈建議線上業務通過業務Server產生下發給用戶端。

開發調試階段

開發調試階段,如果業務Server還沒有產生Token的邏輯,可以暫時參考APIExample上的Token產生邏輯,產生臨時Token,其參考代碼如下:

public final class ARTCTokenHelper {
    /**
     * RTC AppId
     */
    public static String AppId = "";

    /**
     * RTC AppKey
     */
    public static String AppKey = "";

    /**
     * 根據channelId,userId, timestamp, nonce 產生單參數入會 的token
     * Generate a single-parameter meeting token based on channelId, userId, and nonce
     */
    public static String generateSingleParameterToken(String appId, String appKey, String channelId, String userId, long timestamp,  String nonce) {

        StringBuilder stringBuilder = new StringBuilder()
                .append(appId)
                .append(appKey)
                .append(channelId)
                .append(userId)
                .append(timestamp);
        String token =  getSHA256(stringBuilder.toString());
        try{
            JSONObject tokenJson = new JSONObject();
            tokenJson.put("appid", AppId);
            tokenJson.put("channelid", channelId);
            tokenJson.put("userid", userId);
            tokenJson.put("nonce", nonce);
            tokenJson.put("timestamp", timestamp);
            tokenJson.put("token", token);
            String base64Token = Base64.encodeToString(tokenJson.toString().getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
            return base64Token;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根據channelId,userId, timestamp 產生單參數入會 的token
     * Generate a single-parameter meeting token based on channelId, userId, and timestamp
     */
    public static String generateSingleParameterToken(String appId, String appKey, String channelId, String userId, long timestamp) {
        return generateSingleParameterToken(appId, appKey, channelId, userId, timestamp, "");
    }

    public static String getSHA256(String str) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
            return byte2Hex(hash);
        } catch (NoSuchAlgorithmException e) {
            // Consider logging the exception and/or re-throwing as a RuntimeException
            e.printStackTrace();
        }
        return "";
    }

    private static String byte2Hex(byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                // Use single quote for char
                stringBuilder.append('0');
            }
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }

    public static long getTimesTamp() {
        return System.currentTimeMillis() / 1000 + 60 * 60 * 24;
    }
}

3. 建立並初始化引擎

  • 建立 RTC 引擎

調用getInstance建立 RTC 引擎對象。

private AliRtcEngine mAliRtcEngine = null;

if(mAliRtcEngine == null) {
    mAliRtcEngine = AliRtcEngine.getInstance(this);
}
  • 初始化引擎

    • 調用setChannelProfile介面設定頻道為互動模式。

    • 根據業務情境中使用者的角色,調用setClientRole介面為使用者佈建主播/觀眾角色。

    • 調用setAudioProfile介面設定音頻品質與情境模式。

// 設定頻道模式為互動模式,RTC下都使用AliRTCSdkInteractiveLive
mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
// 設定使用者角色,既需要推流也需要拉流使用AliRTCSdkInteractive, 只拉流不推流使用AliRTCSdkLive
if(isAnchor) {
    //如果需要推音視頻流,則設定AliRTCSdkInteractive
    mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
} else {
    //如果只需要拉流,不需要推音視頻流,AliRTCSdkLive
    mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkLive);
}

//設定音頻Profile,預設使用高音質模式AliRtcEngineHighQualityMode及音樂模式AliRtcSceneMusicMode
mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
  • 實現常用回調

SDK 在運行過程中如遇到異常情況,會優先嘗試內部重試機制以自動回復。對於無法自行解決的錯誤,SDK 會通過預定義的回調介面通知您的應用程式。

以下是一些 SDK 無法處理、需由應用程式層監聽和響應的關鍵回調:

異常發生原因

回調及參數

解決方案

說明

鑒權失敗

onJoinChannelResult回調中的result返回AliRtcErrJoinBadToken

發生錯誤時App需要檢查Token是否正確。

在使用者主動調用API時,若鑒權失敗,系統將在調用API的回調中返回鑒權失敗的錯誤資訊。

鑒權將要到期

onWillAuthInfoExpire

發生該異常時App需要重新擷取最新的鑒權資訊後,再調用refreshAuthInfo重新整理鑒權資訊。

鑒權到期錯誤在兩種情況下出現:使用者調用API或程式執行期間。因此,錯誤反饋將通過API回調或通過獨立的錯誤回調通知。

鑒權到期

onAuthInfoExpired

發生該異常時App需要重新入會。

鑒權到期錯誤在兩種情況下出現:使用者調用API或程式執行期間。因此,錯誤反饋將通過API回調或通過獨立的錯誤回調通知。

網路連接異常

onConnectionStatusChange回調返回AliRtcConnectionStatusFailed

發生該異常時APP需要重新入會。

SDK具備一定時間斷網自動回復能力,但若斷線時間超出預設閾值,會觸發逾時並中斷連線。此時,App應檢查網路狀態並指導使用者重新加入會議。

被踢下線

onBye

  • AliRtcOnByeUserReplaced:當發生該異常時排查使用者userid是否相同。

  • AliRtcOnByeBeKickedOut:當發生該異常時,表示被業務踢下線,需要重新入會。

  • AliRtcOnByeChannelTerminated:當發生該異常時,表示房間被銷毀,需要重新入會。

RTC服務提供了管理員可以主動移除參與者的功能。

本地裝置異常

onLocalDeviceException

發生該異常時App需要檢測許可權、裝置硬體是否正常。

RTC服務支援裝置檢測和異常診斷的能力;當本地裝置發生異常時,RTC服務會通過回調的方式通知客戶本地裝置異常,此時,若SDK無法自行解決問題,則App需要介入以查看裝置是否正常。

private AliRtcEngineEventListener mRtcEngineEventListener = new AliRtcEngineEventListener() {
    @Override
    public void onJoinChannelResult(int result, String channel, String userId, int elapsed) {
        super.onJoinChannelResult(result, channel, userId, elapsed);
        handleJoinResult(result, channel, userId);
    }

    @Override
    public void onLeaveChannelResult(int result, AliRtcEngine.AliRtcStats stats){
        super.onLeaveChannelResult(result, stats);
    }

    @Override
    public void onConnectionStatusChange(AliRtcEngine.AliRtcConnectionStatus status, AliRtcEngine.AliRtcConnectionStatusChangeReason reason){
        super.onConnectionStatusChange(status, reason);

        handler.post(new Runnable() {
            @Override
            public void run() {
                if(status == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
                    /* TODO: 務必處理;建議業務提示客戶,此時SDK內部已經嘗試了各種恢複策略已經無法繼續使用時才會上報 */
                    ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                } else {
                    /* TODO: 可選處理;增加業務代碼,一般用於資料統計、UI變化 */
                }
            }
        });
    }
    @Override
    public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
        super.OnLocalDeviceException(deviceType, exceptionType, msg);
        /* TODO: 務必處理;建議業務提示裝置錯誤,此時SDK內部已經嘗試了各種恢複策略已經無法繼續使用時才會上報 */
        handler.post(new Runnable() {
            @Override
            public void run() {
                String str = "OnLocalDeviceException deviceType: " + deviceType + " exceptionType: " + exceptionType + " msg: " + msg;
                ToastHelper.showToast(VideoChatActivity.this, str, Toast.LENGTH_SHORT);
            }
        });
    }

};

private AliRtcEngineNotify mRtcEngineNotify = new AliRtcEngineNotify() {
    @Override
    public void onAuthInfoWillExpire() {
        super.onAuthInfoWillExpire();
        /* TODO: 務必處理;Token即將到期,需要業務觸發重新擷取當前channel,user的鑒權資訊,然後設定refreshAuthInfo即可 */
    }

    @Override
    public void onRemoteUserOnLineNotify(String uid, int elapsed){
        super.onRemoteUserOnLineNotify(uid, elapsed);
    }

    //在onRemoteUserOffLineNotify回調中解除遠端視頻流渲染控制項的設定
    @Override
    public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
        super.onRemoteUserOffLineNotify(uid, reason);
    }

    //在onRemoteTrackAvailableNotify回調中設定遠端視頻流渲染控制項
    @Override
    public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
        handler.post(new Runnable() {
            @Override
            public void run() {
                if(videoTrack == AliRtcVideoTrackCamera) {
                    SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
                    surfaceView.setZOrderMediaOverlay(true);
                    FrameLayout view = getAvailableView();
                    if (view == null) {
                        return;
                    }
                    remoteViews.put(uid, view);
                    view.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                    AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
                    remoteVideoCanvas.view = surfaceView;
                    mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);
                } else if(videoTrack == AliRtcVideoTrackNo) {
                    if(remoteViews.containsKey(uid)) {
                        ViewGroup view = remoteViews.get(uid);
                        if(view != null) {
                            view.removeAllViews();
                            remoteViews.remove(uid);
                            mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
                        }
                    }
                }
            }
        });
    }

    /* 業務可能會觸發同一個UserID的不同裝置搶佔的情況,所以這個地方也需要處理 */
    @Override
    public void onBye(int code){
        handler.post(new Runnable() {
            @Override
            public void run() {
                String msg = "onBye code:" + code;
                ToastHelper.showToast(VideoChatActivity.this, msg, Toast.LENGTH_SHORT);
            }
        });
    }
};

// 設定回調
mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);

4. 設定推拉流屬性

SDK 預設情況下會自動推送和拉取頻道內的音視頻流

  • 設定為觀眾模式後只能拉流,publishLocalAudioStream 無效。

  • 對於主播和觀眾均可以設定為下面的配置。

//SDK預設會publish音頻,對於觀眾此介面無效
mAliRtcEngine.publishLocalAudioStream(true);
//語聊情境,不需要publish視頻
mAliRtcEngine.publishLocalVideoStream(false);

//設定預設訂閱遠端的音頻
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);

5. 加入頻道開始純音頻互動

調用joinChannel介面加入頻道。

說明

如果token是單參數規則產生的,需要調用SDK單參數的joinChannel[1/3]介面,如果是多參數規則產生的,需要調用SDK多參數的joinChannel[2/3]介面。調用完加入頻道後,可以在onJoinChannelResult回調中拿到加入頻道結果,如果result為0,則表示加入頻道成功,否則需要檢查傳進來的Token是否非法。

 mAliRtcEngine.joinChannel(token, null, null, null);

6. 結束純音頻互動

音頻互動結束,需要離開房間並銷毀引擎,按照下列步驟結束音視頻互動

  1. 調用leaveChannel離會。

  2. 調用destroy銷毀引擎,並釋放相關資源。

mAliRtcEngine.leaveChannel();
mAliRtcEngine.destroy();
mAliRtcEngine = null;

7. (可選)觀眾上下麥

業務情境中,如果觀眾角色的使用者想要推流,需要調用setClientRole將觀眾角色切換為主播角色。

// 切換為主播角色
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);

// 切換為觀眾角色
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkLive);

相關文檔