可以使用PEERJS在Android WebView中切换到后摄像头

发布于 2025-02-04 13:00:12 字数 14638 浏览 4 评论 0原文

我一直在尝试使用可以切换相机的Firebase数据库和PEERJ制作一个简单的WebRTC应用程序。我找到了一个教程,它可以正常工作,但是我想在前面和背面切换相机教程中未包含。

androidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    ...

    <uses-feature android:name="android.hardware.camera.any"/>
    <uses-feature android:name="android.hardware.camera"/>

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

    ...

</manifest>

callActivity.java

import static android.view.View.GONE;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

import java.util.UUID;

public class CallActivity extends AppCompatActivity {
    private static final String TAG = CallActivity.class.getSimpleName();

    private final String CAMERA_FRONT = "user";
    private final String CAMERA_BACK = "environment"; // Tried to use it on navigator.mediaDevices.getUserMedia({video: {facingMode: camera}}) but it didn't work.

    private RelativeLayout layoutIncoming, layoutCall, layoutCallControl;
    private Button buttonReject, buttonAccept, buttonCall, buttonAudio, buttonVideo, buttonCamera;
    private EditText editTextCallName;
    private TextView textViewIncoming;

    private WebView webView;

    private String name;
    private String callerName;
    private boolean isPeerConnected = false;

    private DatabaseReference usersRef = FirebaseDatabase.getInstance("link_to_firebase_database").getReference("users");

    private boolean videoEnabled = true;
    private boolean audioEnabled = true;
    private String camera = CAMERA_FRONT;

    private String uniqueID;

    //== Overridden ==//

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);

        layoutIncoming = findViewById(R.id.activity_call_layoutIncoming);
        layoutCall = findViewById(R.id.activity_call_layoutCall);
        layoutCallControl = findViewById(R.id.activity_call_layoutCallControl);

        buttonAccept = findViewById(R.id.activity_call_buttonAccept);
        buttonReject = findViewById(R.id.activity_call_buttonReject);
        buttonCall = findViewById(R.id.activity_call_buttonCall);
        buttonVideo = findViewById(R.id.activity_call_buttonVideo);
        buttonAudio = findViewById(R.id.activity_call_buttonAudio);
        buttonCamera = findViewById(R.id.activity_call_buttonCamera);

        editTextCallName = findViewById(R.id.activity_call_editTextCallName);
        textViewIncoming = findViewById(R.id.activity_call_textViewIncoming);

        webView = findViewById(R.id.activity_call_webView);

        if (getIntent().hasExtra("name")) {
            name = getIntent().getStringExtra("name");
        }

        buttonCall.setOnClickListener(view -> {
            callerName = editTextCallName.getText().toString().trim();
            if (!callerName.isEmpty()) sendCallRequest();
        });

        buttonVideo.setOnClickListener(view -> {
            videoEnabled = !videoEnabled;
            callJsFunction("javascript:toggleVideo(\"" + videoEnabled + "\")");

            if (videoEnabled)
                buttonVideo.setText("Video Off");
            else
                buttonVideo.setText("Video On");
        });

        buttonAudio.setOnClickListener(view -> {
            audioEnabled = !audioEnabled;
            callJsFunction("javascript:toggleAudio(\"" + audioEnabled + "\")");

            if (audioEnabled)
                buttonAudio.setText("Mute");
            else
                buttonAudio.setText("Unmute");
        });

        buttonCamera.setOnClickListener(view -> {
            if (camera.equals(CAMERA_FRONT)) camera = CAMERA_BACK;
            else camera = CAMERA_FRONT;

            switchCamera();
        });

        setupWebView();
    }

    //== Public ==//

    public void onPeerConnected() {
        isPeerConnected = true;
    }

    //== Private ==//

    private void setupWebView() {
        WebChromeClient client = new WebChromeClient() {
            @Override
            public void onPermissionRequest(PermissionRequest request) {
                runOnUiThread(() -> request.grant(request.getResources()));
            }
        };

        webView.setWebChromeClient(client);

        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
        webView.addJavascriptInterface(new JsInterface(this), "Android");

        loadVideoCall();
    }

    private void loadVideoCall() {
        String filePath = "file:///android_asset/call.html";
        webView.loadUrl(filePath);

        WebViewClient client = new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                initializePeer();
            }
        };

        webView.setWebViewClient(client);
    }

    private void initializePeer() {
        uniqueID = getUniqueID();

        callJsFunction("javascript:init(\"" + uniqueID + "\")");

        usersRef.child(name).child("incoming").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                Log.d(TAG, "Received incoming call!!!");
                onCallRequest(snapshot.getValue(String.class));
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

    private void sendCallRequest() {
        if (!isPeerConnected) {
            Toast.makeText(this, "You're not connected to internet. Please try again.", Toast.LENGTH_SHORT).show();
            return;
        }

        usersRef.child(callerName).child("incoming").setValue(name);
        usersRef.child(callerName).child("isAvailable").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                boolean isAvailable = snapshot.getValue() != null? snapshot.getValue(boolean.class): false;

                if (isAvailable) {
                    listenForConnectionID();
                }
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

    private void onCallRequest(String caller) {
        if (caller == null) return;

        String incomingMessage = caller + " is calling...";
        textViewIncoming.setText(incomingMessage);

        buttonAccept.setOnClickListener(view -> {
            usersRef.child(name).child("connectionID").setValue(uniqueID);
            usersRef.child(name).child("isAvailable").setValue(true);

            layoutIncoming.setVisibility(GONE);
            switchToCallControls();
        });

        buttonReject.setOnClickListener(view -> {
            usersRef.child(name).child("incoming").setValue(null);
            layoutIncoming.setVisibility(GONE);
        });

        layoutIncoming.setVisibility(View.VISIBLE);
    }

    private void listenForConnectionID() {
        usersRef.child(callerName).child("connectionID").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                if (snapshot.getValue() == null) return;

                switchToCallControls();
                callJsFunction("javascript:startCall(\"" + snapshot.getValue(String.class) + "\")");
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

    private void switchToCallControls() {
        layoutCall.setVisibility(GONE);
        layoutCallControl.setVisibility(View.VISIBLE);
    }

    private void switchCamera() {
        Log.d(TAG, "switchCamera: " + camera);

        callJsFunction("javascript:switchCamera(\"" + camera + "\")");
    }

    private void callJsFunction(String functionString) {
        webView.post(() -> webView.evaluateJavascript(functionString, value -> Log.d(TAG, value)));
    }

    private String getUniqueID() {
        return UUID.randomUUID().toString();
    }
}

call.html

<!DOCTYPE html>

<html>

    <head>

        <link href="./style.css" rel="stylesheet"/>

    </head>

    <body>

        <script src="./peerjs.js"></script>

        <video class="secondaryVideo" autoplay id="remoteVideo"></video>
        <video class="primaryVideo" autoplay muted id="localVideo"></video>

        <script src="./call.js"></script>

    </body>

</html>

call.js

let localVideo = document.getElementById("localVideo")
let remoteVideo = document.getElementById("remoteVideo")

localVideo.style.opacity = 0
remoteVideo.style.opacity = 0

let peer

function init(userID) {
    peer = new Peer(userID)

    peer.on('open', () => {
        Android.onPeerConnected();
    })

    listen()
}

let localStream

function listen() {
    peer.on('call', (call) => {
        navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true
        }).then(function(mediaStream) {
            localStream = mediaStream

            localVideo.srcObject = localStream
            localVideo.style.opacity = 1

            call.answer(localStream)

            call.on('stream', (remoteStream) => {
                remoteVideo.srcObject = remoteStream
                remoteVideo.style.opacity = 1

                // Swap classes of localVideo and remoteVideo

                localVideo.className = "secondaryVideo"
                remoteVideo.className = "primaryVideo"
            })
        })
    })
}

function startCall(otherUserID) {
    navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true
    }).then(function(mediaStream) {
        localStream = mediaStream

        localVideo.srcObject = localStream
        localVideo.style.opacity = 1

        const call = peer.call(otherUserID, localStream)

        call.on('stream', (remoteStream) => {
            remoteVideo.srcObject = remoteStream
            remoteVideo.style.opacity = 1

            // Swap classes of localVideo and remoteVideo

            localVideo.className = "secondaryVideo"
            remoteVideo.className = "primaryVideo"
        })
    })
}

function toggleVideo(b) {
    if (b == "true") {
        localStream.getVideoTracks()[0].enabled = true
    } else {
        localStream.getVideoTracks()[0].enabled = false
    }
}

function toggleAudio(b) {
    if (b == "true") {
        localStream.getAudioTracks()[0].enabled = true
    } else {
        localStream.getAudioTracks()[0].enabled = false
    }
}

let camIndex = 0

function switchCamera() {
    navigator.mediaDevices.enumerateDevices().then(function(devices) {
        var cameras = []

        devices.forEach(function(device) {
            'videoinput' === device.kind && cameras.push(device.deviceId)
        })

        console.log(cameras.length)

        if (camIndex == cameras.length - 1) {
            camIndex = 0
        } else {
            camIndex = camIndex + 1
        }

        var constraints = {
            video: {deviceId: {exact: cameras[camIndex]}},
            audio: true
        }

        navigator.mediaDevices.getUserMedia(constraints).then(function(mediaStream) {
            localStream = mediaStream

            localVideo.srcObject = localStream

            console.log("camera switched to camIndex " + camIndex) // Only triggered when camIndex = 0
        })
    })
}

我假设camindex = 1是后置摄像头,但它在logcat中给出了此错误消息

D/CallActivity: switchCamera: environment
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
D/CallActivity: null
I/chromium: [INFO:CONSOLE(97)] "2", source: file:///android_asset/call.js (97)
E/libc: Access denied finding property "persist.vendor.camera.privapp.list"
W/ThreadPoolSingl: type=1400 audit(0.0:35101): avc: denied { read } for name="u:object_r:vendor_camera_prop:s0" dev="tmpfs" ino=19669 scontext=u:r:untrusted_app:s0:c161,c256,c512,c768 tcontext=u:object_r:vendor_camera_prop:s0 tclass=file permissive=0
E/cr_VideoCapture: cameraDevice encountered an error
I/chromium: [INFO:CONSOLE(0)] "Uncaught (in promise) NotReadableError: Could not start video source", source: file:///android_asset/call.html (0)
D/CallActivity: switchCamera: user
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
D/CallActivity: null
I/chromium: [INFO:CONSOLE(97)] "2", source: file:///android_asset/call.js (97)
D/: PlayerBase::stop() from IPlayer
D/AudioTrack: stop(398): called with 62088 frames delivered
I/chromium: [INFO:CONSOLE(115)] "camera switched to camIndex 0", source: file:///android_asset/call.js (115)
W/.testapp_webrt: Attempt to remove non-JNI local reference, dumping thread
W/AudioManager: Use of stream types is deprecated for operations other than volume control
W/AudioManager: See the documentation of requestAudioFocus() for what to use instead with android.media.AudioAttributes to qualify your playback use case

I've been trying to make a simple WebRTC app using Firebase Database and PeerJs that can switch cameras. I found one tutorial and it works properly, but I want to switch the camera between front and back which is not included in the tutorial.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    ...

    <uses-feature android:name="android.hardware.camera.any"/>
    <uses-feature android:name="android.hardware.camera"/>

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

    ...

</manifest>

CallActivity.java

import static android.view.View.GONE;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

import java.util.UUID;

public class CallActivity extends AppCompatActivity {
    private static final String TAG = CallActivity.class.getSimpleName();

    private final String CAMERA_FRONT = "user";
    private final String CAMERA_BACK = "environment"; // Tried to use it on navigator.mediaDevices.getUserMedia({video: {facingMode: camera}}) but it didn't work.

    private RelativeLayout layoutIncoming, layoutCall, layoutCallControl;
    private Button buttonReject, buttonAccept, buttonCall, buttonAudio, buttonVideo, buttonCamera;
    private EditText editTextCallName;
    private TextView textViewIncoming;

    private WebView webView;

    private String name;
    private String callerName;
    private boolean isPeerConnected = false;

    private DatabaseReference usersRef = FirebaseDatabase.getInstance("link_to_firebase_database").getReference("users");

    private boolean videoEnabled = true;
    private boolean audioEnabled = true;
    private String camera = CAMERA_FRONT;

    private String uniqueID;

    //== Overridden ==//

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);

        layoutIncoming = findViewById(R.id.activity_call_layoutIncoming);
        layoutCall = findViewById(R.id.activity_call_layoutCall);
        layoutCallControl = findViewById(R.id.activity_call_layoutCallControl);

        buttonAccept = findViewById(R.id.activity_call_buttonAccept);
        buttonReject = findViewById(R.id.activity_call_buttonReject);
        buttonCall = findViewById(R.id.activity_call_buttonCall);
        buttonVideo = findViewById(R.id.activity_call_buttonVideo);
        buttonAudio = findViewById(R.id.activity_call_buttonAudio);
        buttonCamera = findViewById(R.id.activity_call_buttonCamera);

        editTextCallName = findViewById(R.id.activity_call_editTextCallName);
        textViewIncoming = findViewById(R.id.activity_call_textViewIncoming);

        webView = findViewById(R.id.activity_call_webView);

        if (getIntent().hasExtra("name")) {
            name = getIntent().getStringExtra("name");
        }

        buttonCall.setOnClickListener(view -> {
            callerName = editTextCallName.getText().toString().trim();
            if (!callerName.isEmpty()) sendCallRequest();
        });

        buttonVideo.setOnClickListener(view -> {
            videoEnabled = !videoEnabled;
            callJsFunction("javascript:toggleVideo(\"" + videoEnabled + "\")");

            if (videoEnabled)
                buttonVideo.setText("Video Off");
            else
                buttonVideo.setText("Video On");
        });

        buttonAudio.setOnClickListener(view -> {
            audioEnabled = !audioEnabled;
            callJsFunction("javascript:toggleAudio(\"" + audioEnabled + "\")");

            if (audioEnabled)
                buttonAudio.setText("Mute");
            else
                buttonAudio.setText("Unmute");
        });

        buttonCamera.setOnClickListener(view -> {
            if (camera.equals(CAMERA_FRONT)) camera = CAMERA_BACK;
            else camera = CAMERA_FRONT;

            switchCamera();
        });

        setupWebView();
    }

    //== Public ==//

    public void onPeerConnected() {
        isPeerConnected = true;
    }

    //== Private ==//

    private void setupWebView() {
        WebChromeClient client = new WebChromeClient() {
            @Override
            public void onPermissionRequest(PermissionRequest request) {
                runOnUiThread(() -> request.grant(request.getResources()));
            }
        };

        webView.setWebChromeClient(client);

        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
        webView.addJavascriptInterface(new JsInterface(this), "Android");

        loadVideoCall();
    }

    private void loadVideoCall() {
        String filePath = "file:///android_asset/call.html";
        webView.loadUrl(filePath);

        WebViewClient client = new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                initializePeer();
            }
        };

        webView.setWebViewClient(client);
    }

    private void initializePeer() {
        uniqueID = getUniqueID();

        callJsFunction("javascript:init(\"" + uniqueID + "\")");

        usersRef.child(name).child("incoming").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                Log.d(TAG, "Received incoming call!!!");
                onCallRequest(snapshot.getValue(String.class));
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

    private void sendCallRequest() {
        if (!isPeerConnected) {
            Toast.makeText(this, "You're not connected to internet. Please try again.", Toast.LENGTH_SHORT).show();
            return;
        }

        usersRef.child(callerName).child("incoming").setValue(name);
        usersRef.child(callerName).child("isAvailable").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                boolean isAvailable = snapshot.getValue() != null? snapshot.getValue(boolean.class): false;

                if (isAvailable) {
                    listenForConnectionID();
                }
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

    private void onCallRequest(String caller) {
        if (caller == null) return;

        String incomingMessage = caller + " is calling...";
        textViewIncoming.setText(incomingMessage);

        buttonAccept.setOnClickListener(view -> {
            usersRef.child(name).child("connectionID").setValue(uniqueID);
            usersRef.child(name).child("isAvailable").setValue(true);

            layoutIncoming.setVisibility(GONE);
            switchToCallControls();
        });

        buttonReject.setOnClickListener(view -> {
            usersRef.child(name).child("incoming").setValue(null);
            layoutIncoming.setVisibility(GONE);
        });

        layoutIncoming.setVisibility(View.VISIBLE);
    }

    private void listenForConnectionID() {
        usersRef.child(callerName).child("connectionID").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                if (snapshot.getValue() == null) return;

                switchToCallControls();
                callJsFunction("javascript:startCall(\"" + snapshot.getValue(String.class) + "\")");
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

    private void switchToCallControls() {
        layoutCall.setVisibility(GONE);
        layoutCallControl.setVisibility(View.VISIBLE);
    }

    private void switchCamera() {
        Log.d(TAG, "switchCamera: " + camera);

        callJsFunction("javascript:switchCamera(\"" + camera + "\")");
    }

    private void callJsFunction(String functionString) {
        webView.post(() -> webView.evaluateJavascript(functionString, value -> Log.d(TAG, value)));
    }

    private String getUniqueID() {
        return UUID.randomUUID().toString();
    }
}

call.html

<!DOCTYPE html>

<html>

    <head>

        <link href="./style.css" rel="stylesheet"/>

    </head>

    <body>

        <script src="./peerjs.js"></script>

        <video class="secondaryVideo" autoplay id="remoteVideo"></video>
        <video class="primaryVideo" autoplay muted id="localVideo"></video>

        <script src="./call.js"></script>

    </body>

</html>

call.js

let localVideo = document.getElementById("localVideo")
let remoteVideo = document.getElementById("remoteVideo")

localVideo.style.opacity = 0
remoteVideo.style.opacity = 0

let peer

function init(userID) {
    peer = new Peer(userID)

    peer.on('open', () => {
        Android.onPeerConnected();
    })

    listen()
}

let localStream

function listen() {
    peer.on('call', (call) => {
        navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true
        }).then(function(mediaStream) {
            localStream = mediaStream

            localVideo.srcObject = localStream
            localVideo.style.opacity = 1

            call.answer(localStream)

            call.on('stream', (remoteStream) => {
                remoteVideo.srcObject = remoteStream
                remoteVideo.style.opacity = 1

                // Swap classes of localVideo and remoteVideo

                localVideo.className = "secondaryVideo"
                remoteVideo.className = "primaryVideo"
            })
        })
    })
}

function startCall(otherUserID) {
    navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true
    }).then(function(mediaStream) {
        localStream = mediaStream

        localVideo.srcObject = localStream
        localVideo.style.opacity = 1

        const call = peer.call(otherUserID, localStream)

        call.on('stream', (remoteStream) => {
            remoteVideo.srcObject = remoteStream
            remoteVideo.style.opacity = 1

            // Swap classes of localVideo and remoteVideo

            localVideo.className = "secondaryVideo"
            remoteVideo.className = "primaryVideo"
        })
    })
}

function toggleVideo(b) {
    if (b == "true") {
        localStream.getVideoTracks()[0].enabled = true
    } else {
        localStream.getVideoTracks()[0].enabled = false
    }
}

function toggleAudio(b) {
    if (b == "true") {
        localStream.getAudioTracks()[0].enabled = true
    } else {
        localStream.getAudioTracks()[0].enabled = false
    }
}

let camIndex = 0

function switchCamera() {
    navigator.mediaDevices.enumerateDevices().then(function(devices) {
        var cameras = []

        devices.forEach(function(device) {
            'videoinput' === device.kind && cameras.push(device.deviceId)
        })

        console.log(cameras.length)

        if (camIndex == cameras.length - 1) {
            camIndex = 0
        } else {
            camIndex = camIndex + 1
        }

        var constraints = {
            video: {deviceId: {exact: cameras[camIndex]}},
            audio: true
        }

        navigator.mediaDevices.getUserMedia(constraints).then(function(mediaStream) {
            localStream = mediaStream

            localVideo.srcObject = localStream

            console.log("camera switched to camIndex " + camIndex) // Only triggered when camIndex = 0
        })
    })
}

I assume that camIndex = 1 is a back camera but it gives this error message in the logcat

D/CallActivity: switchCamera: environment
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
D/CallActivity: null
I/chromium: [INFO:CONSOLE(97)] "2", source: file:///android_asset/call.js (97)
E/libc: Access denied finding property "persist.vendor.camera.privapp.list"
W/ThreadPoolSingl: type=1400 audit(0.0:35101): avc: denied { read } for name="u:object_r:vendor_camera_prop:s0" dev="tmpfs" ino=19669 scontext=u:r:untrusted_app:s0:c161,c256,c512,c768 tcontext=u:object_r:vendor_camera_prop:s0 tclass=file permissive=0
E/cr_VideoCapture: cameraDevice encountered an error
I/chromium: [INFO:CONSOLE(0)] "Uncaught (in promise) NotReadableError: Could not start video source", source: file:///android_asset/call.html (0)
D/CallActivity: switchCamera: user
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
E/chromium: [ERROR:web_contents_delegate.cc(218)] WebContentsDelegate::CheckMediaAccessPermission: Not supported.
D/CallActivity: null
I/chromium: [INFO:CONSOLE(97)] "2", source: file:///android_asset/call.js (97)
D/: PlayerBase::stop() from IPlayer
D/AudioTrack: stop(398): called with 62088 frames delivered
I/chromium: [INFO:CONSOLE(115)] "camera switched to camIndex 0", source: file:///android_asset/call.js (115)
W/.testapp_webrt: Attempt to remove non-JNI local reference, dumping thread
W/AudioManager: Use of stream types is deprecated for operations other than volume control
W/AudioManager: See the documentation of requestAudioFocus() for what to use instead with android.media.AudioAttributes to qualify your playback use case

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文