Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile> Camera doesn't get opened and user can't go live again when app is kept in background while live streaming and relaunched. #828

Open
shubh435 opened this issue Oct 8, 2024 · 11 comments

Comments

@shubh435
Copy link

shubh435 commented Oct 8, 2024

Android device tested on:- Google pixel 7 &Redmi 13c

Android version tested on:- 14


Steps to reproduce:

Launch the app.
Log in as user with valid credentials.
Tap on the live stream button in the footer .
Tap on the go to live button .
After 5 seconds , get the app in the background and use another app for 4-5 seconds.
Get the current app in the background and launch the Swag tv app from background .
Observe
Workaround:- The user can go live by navigating to other screens and come back to the live streaming screen.

Expected:- When the app is getting to the background during live streaming an informative message for paused live streaming should be shown and on relaunching the app from background the camera should remains opened and live streaming should be resumed.

Actual: Camera doesn't get opened and user can't go live again when app is kept in background while live streaming and relaunched.

Note :- issue 2 > App responds in the same way when the app is unlocked after locking the mobile device.

"@types/react-native": "0.66.1",
"react-native": "0.64.4",

@guoxianzhe
Copy link
Contributor

@shubh435

  1. you can consider of Foreground Service
  2. you can listen onLocalVideoStateChanged and will tell you the reason when the app is background with LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt

@shubh435
Copy link
Author

shubh435 commented Oct 8, 2024

Hi @guoxianzhe thank you for your response
I checked and added the foreground services from this code https://docs.agora.io/en/help/quality-issues/android_background
but nothing has changed
still, the video is stuck when the app goes to the background and opens it again.
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 0, "source": 1, "state": 0} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 0, "source": 1, "state": 0} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 0, "source": 1, "state": 1} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 0, "source": 1, "state": 1} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 14, "source": 1, "state": 3} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 14, "source": 1, "state": 3} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 14, "source": 0, "state": 3} 14
LOG --------LocalVideoStreamReason.LocalVideoStreamReasonDeviceInterrupt {"reason": 14, "source": 0, "state": 3} 14

@shubh435
Copy link
Author

shubh435 commented Oct 8, 2024

@guoxianzhe

I’ve attached a video showing the exact issue. Please take a moment to review it:
Video Link

The problem occurs when the app is sent to the background and then reopened—it either freezes or displays a black screen. I’ve handled the necessary permissions correctly, and my app is still running in the background during this issue.

@guoxianzhe
Copy link
Contributor

@shubh435 I think you should disableVideo when you goes to the background and enableVideo when you opens it manually.

@shubh435
Copy link
Author

@guoxianzhe I tried that way as well but not working

@guoxianzhe
Copy link
Contributor

@shubh435 Could you pls tell me what version react-native-agora are you using?

@shubh435
Copy link
Author

@guoxianzhe this is the version "react-native-agora": "4.3.2"

@guoxianzhe
Copy link
Contributor

@shubh435 Can you check this https://github.com/AgoraIO-Extensions/react-native-agora/tree/v4.3.2/example/src/examples/basic/JoinChannelVideo in your phone? this demo works fine. I think it should help you

@shubh435
Copy link
Author

shubh435 commented Oct 11, 2024

For React Native version 0.72.3, I tested this simple code locally, and it worked fine. However, when tested on React Native version "0.65.3", the code did not function as expected.
where react-native-agora version is the same for both that is "react-native-agora": "4.3.2",

// Import React Hooks
import React, { useRef, useState, useEffect } from 'react';
// Import user interface elements
import {
SafeAreaView,
ScrollView,
StyleSheet,
Text,
View,
Switch,
} from 'react-native';
// Import components related to obtaining Android device permissions
import { PermissionsAndroid, Platform } from 'react-native';
// Import Agora SDK
import {
createAgoraRtcEngine,
ChannelProfileType,
ClientRoleType,
IRtcEngine,
RtcSurfaceView,
RtcConnection,
IRtcEngineEventHandler,
AudienceLatencyLevelType
} from 'react-native-agora';
import { Config } from './config';

// Define basic information
const appId = Config.appId;
const token =Config.token;
const channelName = Config.channelId;
const uid = 0; // Local user Uid, no need to modify

const App = () => {
const agoraEngineRef = useRef(); // IRtcEngine instance
const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
const [isHost, setIsHost] = useState(true); // User role
const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user
const [message, setMessage] = useState(''); // User prompt message
const eventHandler = useRef(); // Implement callback functions

useEffect(() => {
    // Initialize the engine when the App starts
    setupVideoSDKEngine();
    // Release memory when the App is closed
    return () => {
        agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!);
        agoraEngineRef.current?.release();
    };
}, []);

// Define the setupVideoSDKEngine method called when the App starts
const setupVideoSDKEngine = async () => {
    try {
        // Create RtcEngine after obtaining device permissions
        if (Platform.OS === 'android') {
            await getPermission();
        }
        agoraEngineRef.current = createAgoraRtcEngine();
        const agoraEngine = agoraEngineRef.current;
        eventHandler.current = {
            onJoinChannelSuccess: () => {
                showMessage('Successfully joined channel: ' + channelName);
                setIsJoined(true);
            },
            onUserJoined: (_connection: RtcConnection, uid: number) => {
                showMessage('Remote user ' + uid + ' joined');
                setRemoteUid(uid);
            },
            onUserOffline: (_connection: RtcConnection, uid: number) => {
                showMessage('Remote user ' + uid + ' left the channel');
                setRemoteUid(0);
            },
        };

        // Register the event handler
        agoraEngine.registerEventHandler(eventHandler.current);
        // Initialize the engine
        agoraEngine.initialize({
            appId: appId,
        });
        // Enable local video
        agoraEngine.enableVideo();
    } catch (e) {
        console.log(e);
    }
};

// Define the join method called after clicking the join channel button
const join = async () => {
    if (isJoined) {
        return;
    }
    try {
        if (isHost) {
            // Start preview
            agoraEngineRef.current?.startPreview();
            // Join the channel as a broadcaster
            agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                // Set channel profile to live broadcast
                channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                // Set user role to broadcaster
                clientRoleType: ClientRoleType.ClientRoleBroadcaster,
                // Publish audio collected by the microphone
                publishMicrophoneTrack: true,
                // Publish video collected by the camera
                publishCameraTrack: true,
                // Automatically subscribe to all audio streams
                autoSubscribeAudio: true,
                // Automatically subscribe to all video streams
                autoSubscribeVideo: true,
            });
        } else {
            // Join the channel as an audience
            agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                // Set channel profile to live broadcast
                channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                // Set user role to audience
                clientRoleType: ClientRoleType.ClientRoleAudience,
                // Do not publish audio collected by the microphone
                publishMicrophoneTrack: false,
                // Do not publish video collected by the camera
                publishCameraTrack: false,
                // Automatically subscribe to all audio streams
                autoSubscribeAudio: true,
                // Automatically subscribe to all video streams
                autoSubscribeVideo: true,
                // Change the delay level of the audience to achieve ultra-fast live broadcast
                audienceLatencyLevel: AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency,
            });
        }
    } catch (e) {
        console.log(e);
    }
};
// Define the leave method called after clicking the leave channel button
const leave = () => {
    try {
        // Call leaveChannel method to leave the channel
        agoraEngineRef.current?.leaveChannel();
        setRemoteUid(0);
        setIsJoined(false);
        showMessage('Left the channel');
    } catch (e) {
        console.log(e);
    }
};

// Render user interface
return (
    <SafeAreaView style={styles.main}>
        <Text style={styles.head}>Agora Video SDK Quickstart</Text>
        <View style={styles.btnContainer}>
            <Text onPress={join} style={styles.button}>
                Join Channel
            </Text>
            <Text onPress={leave} style={styles.button}>
                Leave Channel
            </Text>
        </View>
        <View style={styles.btnContainer}>
            <Text>Audience</Text>
            <Switch
                onValueChange={switchValue => {
                    setIsHost(switchValue);
                    if (isJoined) {
                        leave();
                    }
                }}
                value={isHost}
            />
            <Text>Host</Text>
        </View>
        <ScrollView
            style={styles.scroll}
            contentContainerStyle={styles.scrollContainer}>
            {isJoined && isHost ? (
                <React.Fragment key={0}>
                    {/* Create a local view using RtcSurfaceView */}
                    <RtcSurfaceView canvas={{ uid: 0 }} style={styles.videoView} />
                    <Text>Local user uid: {uid}</Text>
                </React.Fragment>
            ) : (
                <Text>Join a channel</Text>
            )}
            {isJoined && remoteUid !== 0 ? (
                <React.Fragment key={remoteUid}>
                    {/* Create a remote view using RtcSurfaceView */}
                    <RtcSurfaceView
                        canvas={{ uid: remoteUid }}
                        style={styles.videoView}
                    />
                    <Text>Remote user uid: {remoteUid}</Text>
                </React.Fragment>
            ) : (
                <Text>{isJoined && !isHost ? 'Waiting for remote user to join' : ''}</Text>
            )}
            <Text style={styles.info}>{message}</Text>
        </ScrollView>
    </SafeAreaView>
);

// Display information
function showMessage(msg: string) {
    setMessage(msg);
}

};

// Define user interface styles
const styles = StyleSheet.create({
button: {
paddingHorizontal: 25,
paddingVertical: 4,
fontWeight: 'bold',
color: '#ffffff',
backgroundColor: '#0055cc',
margin: 5,
},
main: { flex: 1, alignItems: 'center' },
scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
scrollContainer: { alignItems: 'center' },
videoView: { width: '90%', height: 200 },
btnContainer: { flexDirection: 'row', justifyContent: 'center' },
head: { fontSize: 20 },
info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' },
});

const getPermission = async () => {
if (Platform.OS === 'android') {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
PermissionsAndroid.PERMISSIONS.CAMERA,
]);
}
};

export default App;

But I need the solution in this "react-native": "0.65.3",

@shubh435
Copy link
Author

@guoxianzhe

@guoxianzhe
Copy link
Contributor

@shubh435 I think this is a react-native issue, maybe you can try to use https://reactnative.dev/docs/appstate to handle camera status manually(remember to re-render your video view manually too).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants