คำอธิบายว่าการเริ่มต้นอย่างรวดเร็วมีอะไรบ้าง

ทวิลิโอทำอะไร?

ทำหน้าที่จัดการผู้ใช้ในห้องและส่งแทร็กวิดีโอและเสียงระหว่างกัน โดยพื้นฐานแล้วสิ่งที่เหลืออยู่ให้เราทำคือ...

  1. ตั้งค่า UI และสภาพแวดล้อมสำหรับการเชื่อมต่อ
  2. จัดการเหตุการณ์ของผู้ใช้

วิธีใช้วิดีโอที่ตั้งโปรแกรมได้ Twilio

ทุกอย่างในบทความนี้นำมาจาก "เอกสาร" ของ Twilio และ "โครงการเริ่มต้นอย่างรวดเร็ว"

ขั้นแรกเราเพิ่ม SDK Twilio ที่ให้มา ประกอบด้วยฟังก์ชันและวัตถุที่จำเป็นทั้งหมด

// inside our app's build.gradle
// Twilio video sdk
implementation 'com.twilio:video-android:$version'

ตั้งค่ามุมมองการแสดงผล

จากนั้นเราสามารถใช้ videoView จาก SDK เพื่อแสดงวิดีโอบนหน้าจอได้

import com.twilio.video.VideoView;

เวอร์ชัน xml คือ

<com.twilio.video.VideoView
    android:id="@+id/primary_video_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:keepScreenOn="true" />

และเวอร์ชันโค้ดคือ

VideoView v = new VideoView(context);
v.setKeepScreenOn(true);

ทำให้อุปกรณ์ตื่นอยู่เสมอ

โปรดสังเกตว่าฉันใส่แอตทริบิวต์ setKeepScreenon เนื่องจากเรามักไม่ต้องการให้โทรศัพท์เข้าสู่โหมดสลีประหว่างแฮงเอาท์วิดีโอ สะดวกมากที่ keepScreenOn สามารถใช้กับมุมมองย่อยใดก็ได้ และจะทำงานในลักษณะเดียวกับที่ใช้ในมุมมองพาเรนต์

เชื่อมต่อกับห้อง

สร้างโทเค็นการเข้าถึง

ผู้ใช้ทุกคนจำเป็นต้องใช้โทเค็นการเข้าถึงเพื่อเชื่อมต่อกับห้อง เป็นเอกลักษณ์สำหรับผู้ใช้ทุกคนในห้องประชุมทุกห้อง ขั้นตอนนี้มักจะทำในแบ็กเอนด์

// taken from https://www.twilio.com/docs/iam/access-tokens?code-sample=code-creating-an-access-token-video-2&code-language=Java&code-sdk-version=7.x
import com.twilio.jwt.accesstoken.AccessToken;
import com.twilio.jwt.accesstoken.VideoGrant;

public class TokenGenerator {

  public static void main(String[] args) {
    // Required for all types of tokens
    String twilioAccountSid = "ACxxxxxxxxxxxx";
    String twilioApiKey = "SKxxxxxxxxxxxx";
    String twilioApiSecret = "xxxxxxxxxxxxxx";

    // Required for Video
    String identity = "user";

    // Create Video grant
    VideoGrant grant = new VideoGrant().setRoom("cool room");

    // Create access token
    AccessToken token = new AccessToken.Builder(
      twilioAccountSid,
      twilioApiKey,
      twilioApiSecret
    ).identity(identity).grant(grant).build();

    System.out.println(token.toJwt());
  }
}

จากนั้นในแอปพลิเคชันส่วนหน้าของเรา เราสามารถเชื่อมต่อกับห้องโดยใช้โทเค็นการเข้าถึงได้ ชื่อห้องถูกกำหนดไว้แล้วเมื่อสร้างโทเค็นการเข้าถึง ดังนั้นอาร์กิวเมนต์ที่นี่จะไม่ส่งผลต่อห้องที่เราจะเชื่อมต่อจริงๆ

// taken from https://www.twilio.com/docs/video/android-getting-started
public void connectToRoom(String roomName) {
  ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .audioTracks(localAudioTracks)
    .videoTracks(localVideoTracks)
    .dataTracks(localDataTracks)
    .build();
  room = Video.connect(context, connectOptions, roomListener);
}

สร้างแทร็กวิดีโอและเสียงในเครื่อง

เพื่อให้ connectToRoom() ทำงาน เราต้องแชร์วิดีโอและเสียงของเรากับผู้เข้าร่วมคนอื่นๆ ผ่านทางแทร็ก

private void createAudioAndVideoTracks() {
    // Share your microphone
    localAudioTrack = LocalAudioTrack.create(this, true, LOCAL_AUDIO_TRACK_NAME);

    // Share your camera
    cameraCapturerCompat = new CameraCapturerCompat(this, getAvailableCameraSource());
    localVideoTrack = LocalVideoTrack.create(this,
            true,
            cameraCapturerCompat.getVideoCapturer(),
            LOCAL_VIDEO_TRACK_NAME);
    primaryVideoView.setMirror(true);
    localVideoTrack.addRenderer(primaryVideoView);
    localVideoView = primaryVideoView;
}

ขออนุญาติไมค์และกล้องครับ

ในรายการของเรา เราต้องการสิ่งเหล่านี้ทั้งหมด

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

จากนั้นเราก็สามารถตรวจสอบและขออนุญาตได้ในที่สุด

private boolean checkPermissionForCameraAndMicrophone() {
        int resultCamera = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA);
        int resultMic = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO);
        return resultCamera == PackageManager.PERMISSION_GRANTED &&
                resultMic == PackageManager.PERMISSION_GRANTED;
    }

    private void requestPermissionForCameraAndMicrophone() {

            // request permission in fragment
            requestPermissions(
                    new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO},
                    CAMERA_MIC_PERMISSION_REQUEST_CODE);

    }

และเราต้องจับเหตุการณ์เมื่อได้รับอนุญาตแล้ว

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    if (requestCode == CAMERA_MIC_PERMISSION_REQUEST_CODE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
    ) {
        Toast.makeText(context, "Permission granted", Toast.LENGTH_LONG).show();
    }
}

จัดการเหตุการณ์ของผู้ใช้

Twilio จะรายงานเหตุการณ์ต่างๆ เช่น ผู้เข้าร่วมไม่ได้เชื่อมต่อ ถูกตัดการเชื่อมต่อผ่านการโทรกลับ เราต้องจัดให้มีอินเทอร์เฟซ roomListener() เมื่อเราเชื่อมต่อกับห้อง

นอกจากการตั้งค่าผู้ฟังสำหรับห้องแล้ว เราควรแนบผู้ฟังกับผู้เข้าร่วมทุกคนในห้องด้วย ผู้ฟังรายนี้จะติดตามสถานะวิดีโอและเสียงของผู้เข้าร่วมรายนั้น

เราสามารถดูวิธีการทั้งหมดสำหรับการโทรกลับเหล่านี้ได้โดยดูจาก "ตัวอย่างการเริ่มต้นอย่างรวดเร็ว" ของ Twilio

ด้านล่างนี้เป็นตัวอย่างสำหรับ roomListener() สิ่งที่เราต้องการทำที่นี่คือทันทีที่มีการเรียก onConnected() เราจะเรียกผู้เข้าร่วมทั้งหมดในห้องและแนบ remoteParticipantListener() ไว้กับแต่ละคน

แน่นอนว่า เรายังต้องแนบผู้ฟังกับผู้เข้าร่วมที่จะเข้าร่วมห้องในอนาคตด้วย

private Room.Listener roomListener() {
    return new Room.Listener() {
        @Override
        public void onConnected(Room room) {
            Toast.makeText(MainActivity.this, "Connected to room " + room.getName(), Toast.LENGTH_LONG).show();

            localParticipant = room.getLocalParticipant();

            List<RemoteParticipant> remoteParticipants = room.getRemoteParticipants();

            for (RemoteParticipant remoteParticipant : remoteParticipants) {

                addRemoteParticipant(remoteParticipant);
            }

            // adding the participant does not mean their video track is ready to be subscribed
            // display the videos in the onVideoTrackSubscribed callback
        }

        @Override
        public void onParticipantConnected(Room room, RemoteParticipant remoteParticipant) {
            addRemoteParticipant(remoteParticipant);
        }

        @Override
        public void onParticipantDisconnected(Room room, RemoteParticipant remoteParticipant) {
            removeRemoteParticipant(remoteParticipant);
        }
}

เนื่องจากนี่คืออินเทอร์เฟซ Java เราจึงต้องทำตามวิธีการที่จำเป็นทั้งหมดให้ครบถ้วน แต่โชคดีที่ชื่อวิธีการนั้นอธิบายได้ในตัว และตรรกะในการแสดงและลบวิดีโอของผู้เข้าร่วมออกจาก UI นั้นตรงไปตรงมา

ด้านล่างนี้เป็นตัวอย่างสำหรับการเรียกกลับ remoteParticipantListener() โปรดจำไว้ว่านี่คือผู้ฟังสำหรับผู้เข้าร่วมแต่ละคน ดังนั้นให้อัปเดตการแสดงผลที่ถูกต้องสำหรับผู้เข้าร่วมที่ถูกต้อง

// add listener to each participant when you joined a room

@SuppressLint("SetTextI18n")
private RemoteParticipant.Listener remoteParticipantListener() {
    return new RemoteParticipant.Listener() {
        @Override
        public void onAudioTrackPublished(RemoteParticipant remoteParticipant,
                                          RemoteAudioTrackPublication remoteAudioTrackPublication) {
            Log.i(TAG, String.format("onAudioTrackPublished: " +
                            "[RemoteParticipant: identity=%s], " +
                            "[RemoteAudioTrackPublication: sid=%s, enabled=%b, " +
                            "subscribed=%b, name=%s]",
                    remoteParticipant.getIdentity(),
                    remoteAudioTrackPublication.getTrackSid(),
                    remoteAudioTrackPublication.isTrackEnabled(),
                    remoteAudioTrackPublication.isTrackSubscribed(),
                    remoteAudioTrackPublication.getTrackName()));
        }

        @Override
        public void onAudioTrackUnpublished(RemoteParticipant remoteParticipant,
                                            RemoteAudioTrackPublication remoteAudioTrackPublication) {
            Log.i(TAG, String.format("onAudioTrackUnpublished: " +
                            "[RemoteParticipant: identity=%s], " +
                            "[RemoteAudioTrackPublication: sid=%s, enabled=%b, " +
                            "subscribed=%b, name=%s]",
                    remoteParticipant.getIdentity(),
                    remoteAudioTrackPublication.getTrackSid(),
                    remoteAudioTrackPublication.isTrackEnabled(),
                    remoteAudioTrackPublication.isTrackSubscribed(),
                    remoteAudioTrackPublication.getTrackName()));
        }
......

ทำความสะอาดและปล่อยทรัพยากร

สุดท้ายนี้ อย่าลืมจัดการกับการเชื่อมต่อใหม่และการตัดการเชื่อมต่อตามวงจรชีวิตกิจกรรมของเรา

หากแอปอื่นๆ ที่มีลำดับความสำคัญสูงกว่าขัดจังหวะแอปหรือผู้ใช้ของเราปิดหน้าจอด้วยตนเองโดยการคลิกปุ่มเปิด/ปิด เราควรตัดการเชื่อมต่อจากห้อง เนื่องจากผู้ใช้อาจไม่กลับมา และ Twilio จะยังคงนับนาทีการบริการและเรียกเก็บเงินต่อไป

ดังนั้นวิธี onPause() ของเราควรมีการตัดการเชื่อมต่อจากห้องเป็นขั้นต่ำและปล่อยทรัพยากรกล้องและไมโครโฟนในโทรศัพท์ของเรา

@Override
public void onPause(){
    super.onPause();


    /*
     * Release the local video track before going in the background. This ensures that the
     * camera can be used by other applications while this app is in the background.
     */
    if (localVideoTrack != null) {
        /*
         * If this local video track is being shared in a Room, unpublish from room before
         * releasing the video track. Participants will be notified that the track has been
         * unpublished.
         */
        if (localParticipant != null) {
            localParticipant.unpublishTrack(localVideoTrack);
        }

        localVideoTrack.release();
        localVideoTrack = null;
    }

    /*
     * Always disconnect from the room before leaving the Activity to
     * ensure any memory allocated to the Room resource is freed.
     */
    if (room != null && room.getState() != Room.State.DISCONNECTED) {
        room.disconnect();
        clearVideoDisplays();
    }
|

และใน onResume()เราจะเชื่อมต่อผู้ใช้กับห้องอีกครั้ง หากผู้ใช้กลับมาที่กิจกรรมของเรา

วิธีการส่งเสียงไปยังอุปกรณ์ต่าง ๆ

ในกรณีที่ผู้ใช้เสียบปลั๊กอุดหูหรือเชื่อมต่อกับหูฟังบลูทูธ แอปของเราต้องตรวจจับเหตุการณ์นี้และกำหนดเส้นทางแทร็กเสียงไปยังอุปกรณ์นั้น

ไลบรารี AudioSwitch ของ Twilio ช่วยให้เราทำสิ่งนั้นได้

ไลบรารีจะเลือกอุปกรณ์โดยอัตโนมัติตามลำดับความสำคัญต่อไปนี้: BluetoothHeadset -> WiredHeadset -> Earpiece -> Speakerphone

หากเรากำลังทำแอปการประชุมผ่านวิดีโอ เราจะเลือกใช้สปีกเกอร์โฟนมากกว่าหูฟัง เราสามารถกำหนดลำดับความสำคัญในตัวสร้างได้

List<Class<? extends AudioDevice>> preferredDevices = new ArrayList<>();
preferredDevices.add(BluetoothHeadset.class);
preferredDevices.add(WiredHeadset.class);
preferredDevices.add(Speakerphone.class);
AudioSwitch audioSwitch = new AudioSwitch(context, false, focusChange -> {}, preferredDevices);
  1. สร้างวัตถุ audioSwitch ด้วยรายการลำดับความสำคัญ
  2. ลงทะเบียนการโทรกลับ audioSwitch.start() ก่อนที่จะโทรเปิดใช้งาน
  3. โทร audioSwitch.activate() เมื่อเชื่อมต่อ ToRoom
  4. เรียก audioSwitch.deactive() เมื่อตัดการเชื่อมต่อจากห้อง
  5. เรียก audioSwitch.stop() ไปที่ onDestroy()

วิธีทำให้หน้าจอเป็นสีดำเมื่อผู้เข้าร่วมคนอื่นปิดการใช้งานวิดีโอของตน

คุณอาจสังเกตเห็นว่าหน้าจอของคุณแสดงเฟรมสุดท้ายที่ค้างของผู้เข้าร่วมรายอื่นเมื่อเขาปิดใช้งานวิดีโอของเขา วิธีที่เหมาะสมกว่าในการจัดการสิ่งนี้คือการแสดงหน้าจอสีดำ

ฉันไม่พบการตั้งค่าใดๆ ที่ทำให้หน้าจอเป็นสีดำโดยอัตโนมัติ ในที่สุดฉันก็สร้างเค้าโครงข้อจำกัดโดยตั้งค่าพื้นหลังเป็นสีดำ ในตอนแรกทำเครื่องหมายการมองเห็นเป็น GONE และเมื่อฉันได้รับแจ้งให้ผู้เข้าร่วมปิดการใช้งานการโทรกลับทางวิดีโอ ฉันจะตั้งค่ามุมมองกลับไปเป็นมองเห็นได้เพื่อปกปิดเฟรมสุดท้ายที่ค้าง

วิธีตรวจจับผู้พูดหลัก

บางครั้งเราอาจต้องการตอบสนองต่อผู้พูด เช่นเดียวกับการใส่วิดีโอของผู้เข้าร่วมที่กำลังพูดอยู่ในหน้าต่างหลัก

Twilio ให้การตั้งค่า enableDominantSpeaker แก่เราเพื่อตรวจจับเหตุการณ์นี้

ดู "https://www.twilio.com/docs/video/detecting-dominant-speaker" เพื่อเปิดใช้งานการตั้งค่านี้ใน ConnectOptions

ตอนนี้เรามี onDominantSpeakerChanged โทรกลับแล้ว

วิธีหลีกเลี่ยงการได้ยินเสียงสะท้อน

ฉันพบปัญหาเสียงสะท้อนระหว่าง Samsung และ iPhone ผู้ใช้จะได้ยินเสียงของเขากลับมาจากอุปกรณ์

การใช้การตั้งค่าที่แนะนำจาก "https://github.com/twilio/video-quickstart-android#troubleshooting-audio" ช่วยแก้ไขปัญหานี้ได้