mirror of
https://github.com/pierre42100/ComunicAndroid
synced 2024-11-23 13:59:29 +00:00
Can record video of video calls
This commit is contained in:
parent
7381091c08
commit
8f87b05fcc
@ -57,7 +57,7 @@ public class StringsUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format timestamp to string
|
* Format timestamp to string (date only)
|
||||||
*
|
*
|
||||||
* @param time The time to format
|
* @param time The time to format
|
||||||
* @return Generated string
|
* @return Generated string
|
||||||
@ -68,6 +68,18 @@ public class StringsUtils {
|
|||||||
return simpleDateFormat.format((long)1000*time);
|
return simpleDateFormat.format((long)1000*time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format timestamp to string (date + time)
|
||||||
|
*
|
||||||
|
* @param time The time to format
|
||||||
|
* @return Generated string
|
||||||
|
*/
|
||||||
|
public static String FormatDateTime(int time){
|
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
|
||||||
|
Locale.getDefault());
|
||||||
|
return simpleDateFormat.format((long)1000*time);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an integer into a string, making sure that the generated string respects an minimum
|
* Convert an integer into a string, making sure that the generated string respects an minimum
|
||||||
* size
|
* size
|
||||||
|
@ -137,4 +137,21 @@ public final class Constants {
|
|||||||
public static final String PREFERENCE_ACCELERATE_NOTIFICATIONS_REFRESH
|
public static final String PREFERENCE_ACCELERATE_NOTIFICATIONS_REFRESH
|
||||||
= "accelerate_notifications_refresh";
|
= "accelerate_notifications_refresh";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External storage directory
|
||||||
|
*/
|
||||||
|
public final class EXTERNAL_STORAGE {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main storage directory
|
||||||
|
*/
|
||||||
|
public static final String MAIN_DIRECTORY_NAME = "Comunic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Video calls directory
|
||||||
|
*/
|
||||||
|
public static final String VIDEO_CALLS_STORAGE_DIRECTORY = "VideoCalls";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,12 @@ import android.os.AsyncTask;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@ -22,12 +24,14 @@ import org.communiquons.android.comunic.client.data.models.CallMember;
|
|||||||
import org.communiquons.android.comunic.client.data.models.CallResponse;
|
import org.communiquons.android.comunic.client.data.models.CallResponse;
|
||||||
import org.communiquons.android.comunic.client.data.models.CallsConfiguration;
|
import org.communiquons.android.comunic.client.data.models.CallsConfiguration;
|
||||||
import org.communiquons.android.comunic.client.data.utils.AccountUtils;
|
import org.communiquons.android.comunic.client.data.utils.AccountUtils;
|
||||||
|
import org.communiquons.android.comunic.client.data.utils.StringsUtils;
|
||||||
import org.communiquons.android.comunic.client.ui.arrays.CallPeersConnectionsList;
|
import org.communiquons.android.comunic.client.ui.arrays.CallPeersConnectionsList;
|
||||||
import org.communiquons.android.comunic.client.ui.asynctasks.GetCallInformationTask;
|
import org.communiquons.android.comunic.client.ui.asynctasks.GetCallInformationTask;
|
||||||
import org.communiquons.android.comunic.client.ui.asynctasks.HangUpCallTask;
|
import org.communiquons.android.comunic.client.ui.asynctasks.HangUpCallTask;
|
||||||
import org.communiquons.android.comunic.client.ui.asynctasks.RespondToCallTask;
|
import org.communiquons.android.comunic.client.ui.asynctasks.RespondToCallTask;
|
||||||
import org.communiquons.android.comunic.client.ui.models.CallPeerConnection;
|
import org.communiquons.android.comunic.client.ui.models.CallPeerConnection;
|
||||||
import org.communiquons.android.comunic.client.ui.receivers.PendingCallsBroadcastReceiver;
|
import org.communiquons.android.comunic.client.ui.receivers.PendingCallsBroadcastReceiver;
|
||||||
|
import org.communiquons.android.comunic.client.ui.utils.FilesUtils;
|
||||||
import org.communiquons.android.comunic.client.ui.utils.PermissionsUtils;
|
import org.communiquons.android.comunic.client.ui.utils.PermissionsUtils;
|
||||||
import org.communiquons.android.comunic.client.ui.utils.UiUtils;
|
import org.communiquons.android.comunic.client.ui.utils.UiUtils;
|
||||||
import org.communiquons.signalexchangerclient.SignalExchangerCallback;
|
import org.communiquons.signalexchangerclient.SignalExchangerCallback;
|
||||||
@ -43,11 +47,16 @@ import org.webrtc.SessionDescription;
|
|||||||
import org.webrtc.StatsReport;
|
import org.webrtc.StatsReport;
|
||||||
import org.webrtc.SurfaceViewRenderer;
|
import org.webrtc.SurfaceViewRenderer;
|
||||||
import org.webrtc.VideoCapturer;
|
import org.webrtc.VideoCapturer;
|
||||||
|
import org.webrtc.VideoFileRenderer;
|
||||||
import org.webrtc.VideoFrame;
|
import org.webrtc.VideoFrame;
|
||||||
import org.webrtc.VideoSink;
|
import org.webrtc.VideoSink;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.communiquons.android.comunic.client.data.utils.TimeUtils.time;
|
||||||
|
import static org.communiquons.android.comunic.client.ui.Constants.EXTERNAL_STORAGE.VIDEO_CALLS_STORAGE_DIRECTORY;
|
||||||
import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FILL;
|
import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FILL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,6 +105,13 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
private boolean mIsCameraStopped = false;
|
private boolean mIsCameraStopped = false;
|
||||||
private boolean mIsMicrophoneStopped = false;
|
private boolean mIsMicrophoneStopped = false;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify whether we are recording video or not
|
||||||
|
*/
|
||||||
|
private boolean mIsRecordingVideo = false;
|
||||||
|
private VideoFileRenderer mVideoFileRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connections list
|
* Connections list
|
||||||
*/
|
*/
|
||||||
@ -119,6 +135,7 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
private View mButtonsView;
|
private View mButtonsView;
|
||||||
private ImageButton mStopCameraButton;
|
private ImageButton mStopCameraButton;
|
||||||
private ImageButton mStopMicrophoneButton;
|
private ImageButton mStopMicrophoneButton;
|
||||||
|
private ImageButton mMoreActionsButton;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -203,6 +220,9 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
|
|
||||||
mStopMicrophoneButton = findViewById(R.id.stopMicrophoneButton);
|
mStopMicrophoneButton = findViewById(R.id.stopMicrophoneButton);
|
||||||
mStopMicrophoneButton.setOnClickListener(v -> toggleStopMicrophone());
|
mStopMicrophoneButton.setOnClickListener(v -> toggleStopMicrophone());
|
||||||
|
|
||||||
|
mMoreActionsButton = findViewById(R.id.moreActionsButton);
|
||||||
|
mMoreActionsButton.setOnClickListener(v -> showMoreActions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -337,6 +357,7 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
mList.add(callPeer);
|
mList.add(callPeer);
|
||||||
|
|
||||||
EglBase eglBase = EglBase.create();
|
EglBase eglBase = EglBase.create();
|
||||||
|
callPeer.setEglRenderer(eglBase);
|
||||||
|
|
||||||
//Create peer connection
|
//Create peer connection
|
||||||
PeerConnectionClient peerConnectionClient = new PeerConnectionClient(
|
PeerConnectionClient peerConnectionClient = new PeerConnectionClient(
|
||||||
@ -403,6 +424,12 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
callPeer.getRemoteSinks().add(remoteProxyRenderer);
|
callPeer.getRemoteSinks().add(remoteProxyRenderer);
|
||||||
callPeer.setRemoteProxyRenderer(remoteProxyRenderer);
|
callPeer.setRemoteProxyRenderer(remoteProxyRenderer);
|
||||||
|
|
||||||
|
|
||||||
|
ProxyVideoSink recordProxyVideoSink = new ProxyVideoSink();
|
||||||
|
callPeer.getRemoteSinks().add(recordProxyVideoSink);
|
||||||
|
callPeer.setRecordProxyRenderer(recordProxyVideoSink);
|
||||||
|
|
||||||
|
|
||||||
//Start connection
|
//Start connection
|
||||||
peerConnectionClient.createPeerConnection(
|
peerConnectionClient.createPeerConnection(
|
||||||
mLocalProxyVideoSink,
|
mLocalProxyVideoSink,
|
||||||
@ -423,6 +450,9 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
mHangUpButton.setVisibility(View.GONE);
|
mHangUpButton.setVisibility(View.GONE);
|
||||||
mStopped = true;
|
mStopped = true;
|
||||||
|
|
||||||
|
//Stop recording video
|
||||||
|
stopVideoRecord();
|
||||||
|
|
||||||
if(mRefreshCallInformation != null)
|
if(mRefreshCallInformation != null)
|
||||||
mRefreshCallInformation.interrupt();
|
mRefreshCallInformation.interrupt();
|
||||||
|
|
||||||
@ -449,6 +479,11 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
if(callPeer == null)
|
if(callPeer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
//Check if it is the first peer, if yes stop recording
|
||||||
|
if(mList.get(0) == callPeer)
|
||||||
|
stopVideoRecord();
|
||||||
|
|
||||||
|
|
||||||
((ProxyVideoSink)callPeer.getRemoteProxyRenderer()).setTarget(null);
|
((ProxyVideoSink)callPeer.getRemoteProxyRenderer()).setTarget(null);
|
||||||
|
|
||||||
callPeer.getPeerConnectionClient().close();
|
callPeer.getPeerConnectionClient().close();
|
||||||
@ -500,6 +535,87 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
|||||||
mButtonsView.setVisibility(show ? View.VISIBLE : View.GONE);
|
mButtonsView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showMoreActions(){
|
||||||
|
|
||||||
|
PopupMenu popupMenu = new PopupMenu(this, mMoreActionsButton);
|
||||||
|
popupMenu.inflate(R.menu.menu_call_more_actions);
|
||||||
|
|
||||||
|
popupMenu.getMenu().findItem(
|
||||||
|
mIsRecordingVideo ? R.id.action_record_video : R.id.action_stop_record_video
|
||||||
|
).setVisible(false);
|
||||||
|
|
||||||
|
popupMenu.setOnMenuItemClickListener(this::onChooseAction);
|
||||||
|
popupMenu.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onChooseAction(MenuItem item){
|
||||||
|
|
||||||
|
if(item.getItemId() == R.id.action_record_video){
|
||||||
|
startVideoRecord();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item.getItemId() == R.id.action_stop_record_video){
|
||||||
|
stopVideoRecord();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start to record video
|
||||||
|
*
|
||||||
|
* Warning ! There is currently a technical limitation : only the first connection will
|
||||||
|
* be recorded ! And as soon as a connection is closed, the record is stopped
|
||||||
|
*/
|
||||||
|
private void startVideoRecord(){
|
||||||
|
|
||||||
|
if(mList.size() == 0){
|
||||||
|
Toast.makeText(this, R.string.err_call_no_peer_connected, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CallPeerConnection callPeer = mList.get(0);
|
||||||
|
|
||||||
|
//Create target file
|
||||||
|
String filename = StringsUtils.FormatDateTime(time()).replace(":", "-") + ".mp4";
|
||||||
|
File file = FilesUtils.GetExternalStorageFile(VIDEO_CALLS_STORAGE_DIRECTORY, filename);
|
||||||
|
|
||||||
|
if(file == null || file.exists()){
|
||||||
|
Toast.makeText(this, R.string.err_can_not_create_record_file, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create video file renderer
|
||||||
|
try {
|
||||||
|
mVideoFileRenderer = new VideoFileRenderer(file.getAbsolutePath(),
|
||||||
|
640, 480,
|
||||||
|
callPeer.getEglRenderer().getEglBaseContext());
|
||||||
|
|
||||||
|
|
||||||
|
((ProxyVideoSink)callPeer.getRecordProxyRenderer()).
|
||||||
|
setTarget(mVideoFileRenderer);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Toast.makeText(this, R.string.err_initialize_video_call_recording, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
mIsRecordingVideo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopVideoRecord(){
|
||||||
|
|
||||||
|
if(!mIsRecordingVideo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProxyVideoSink recordVideoSink =
|
||||||
|
(ProxyVideoSink) mList.get(0).getRecordProxyRenderer();
|
||||||
|
mIsRecordingVideo = false;
|
||||||
|
recordVideoSink.setTarget(null);
|
||||||
|
mVideoFileRenderer.release();
|
||||||
|
}
|
||||||
|
|
||||||
//Based on https://github.com/vivek1794/webrtc-android-codelab
|
//Based on https://github.com/vivek1794/webrtc-android-codelab
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -2,6 +2,7 @@ package org.communiquons.android.comunic.client.ui.models;
|
|||||||
|
|
||||||
import org.appspot.apprtc.PeerConnectionClient;
|
import org.appspot.apprtc.PeerConnectionClient;
|
||||||
import org.communiquons.android.comunic.client.data.models.CallMember;
|
import org.communiquons.android.comunic.client.data.models.CallMember;
|
||||||
|
import org.webrtc.EglBase;
|
||||||
import org.webrtc.SurfaceViewRenderer;
|
import org.webrtc.SurfaceViewRenderer;
|
||||||
import org.webrtc.VideoSink;
|
import org.webrtc.VideoSink;
|
||||||
|
|
||||||
@ -19,7 +20,9 @@ public class CallPeerConnection {
|
|||||||
private PeerConnectionClient peerConnectionClient;
|
private PeerConnectionClient peerConnectionClient;
|
||||||
private boolean connected = false;
|
private boolean connected = false;
|
||||||
private VideoSink remoteProxyRenderer;
|
private VideoSink remoteProxyRenderer;
|
||||||
|
private VideoSink recordProxyRenderer;
|
||||||
private ArrayList<VideoSink> remoteSinks = new ArrayList<>();
|
private ArrayList<VideoSink> remoteSinks = new ArrayList<>();
|
||||||
|
private EglBase eglRenderer;
|
||||||
|
|
||||||
//Views
|
//Views
|
||||||
private SurfaceViewRenderer mRemoteViewView;
|
private SurfaceViewRenderer mRemoteViewView;
|
||||||
@ -75,4 +78,20 @@ public class CallPeerConnection {
|
|||||||
public void setRemoteProxyRenderer(VideoSink remoteProxyRenderer) {
|
public void setRemoteProxyRenderer(VideoSink remoteProxyRenderer) {
|
||||||
this.remoteProxyRenderer = remoteProxyRenderer;
|
this.remoteProxyRenderer = remoteProxyRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EglBase getEglRenderer() {
|
||||||
|
return eglRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEglRenderer(EglBase eglRenderer) {
|
||||||
|
this.eglRenderer = eglRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoSink getRecordProxyRenderer() {
|
||||||
|
return recordProxyRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecordProxyRenderer(VideoSink recordProxyRenderer) {
|
||||||
|
this.recordProxyRenderer = recordProxyRenderer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package org.communiquons.android.comunic.client.ui.utils;
|
||||||
|
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import static org.communiquons.android.comunic.client.ui.Constants.EXTERNAL_STORAGE.MAIN_DIRECTORY_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Files utilities
|
||||||
|
*
|
||||||
|
* @author Pierre HUBERT
|
||||||
|
*/
|
||||||
|
public class FilesUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link File} object for a file present outside of the application directories
|
||||||
|
*
|
||||||
|
* @param subdirectory Comunic storage subdirectory
|
||||||
|
* @param filename The name of the target file
|
||||||
|
* @return File pointer / null in case of failure
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static File GetExternalStorageFile(String subdirectory, String filename){
|
||||||
|
|
||||||
|
try {
|
||||||
|
File container = new File(
|
||||||
|
Environment.getExternalStorageDirectory() + "/" + MAIN_DIRECTORY_NAME,
|
||||||
|
subdirectory);
|
||||||
|
|
||||||
|
if (!container.exists())
|
||||||
|
if (!container.mkdirs())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new File(container, filename);
|
||||||
|
|
||||||
|
} catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -101,6 +101,18 @@
|
|||||||
android:src="@drawable/ic_mic"
|
android:src="@drawable/ic_mic"
|
||||||
android:tint="@android:color/white" />
|
android:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/moreActionsButton"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:contentDescription="@string/action_more"
|
||||||
|
android:src="@drawable/ic_more"
|
||||||
|
android:tint="@android:color/white" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
12
app/src/main/res/menu/menu_call_more_actions.xml
Normal file
12
app/src/main/res/menu/menu_call_more_actions.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_record_video"
|
||||||
|
android:title="@string/action_record_video"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_stop_record_video"
|
||||||
|
android:title="@string/action_stop_record_video"/>
|
||||||
|
|
||||||
|
</menu>
|
@ -339,4 +339,9 @@
|
|||||||
<string name="err_save_image_in_gallery">Une erreur a survenue lors de l\'enregistrement de l\'image dans la gallerie !</string>
|
<string name="err_save_image_in_gallery">Une erreur a survenue lors de l\'enregistrement de l\'image dans la gallerie !</string>
|
||||||
<string name="success_save_image_in_gallery">L\'image a bien été enregistrée dans la gallerie !</string>
|
<string name="success_save_image_in_gallery">L\'image a bien été enregistrée dans la gallerie !</string>
|
||||||
<string name="err_missing_call_config">Configuration des appels vidéos manquante !</string>
|
<string name="err_missing_call_config">Configuration des appels vidéos manquante !</string>
|
||||||
|
<string name="action_record_video">Enregistrer la vidéo</string>
|
||||||
|
<string name="action_stop_record_video">Arrêter l\'enregistrement vidéo</string>
|
||||||
|
<string name="err_call_no_peer_connected">Cet appel n\'est relié à aucun pair !</string>
|
||||||
|
<string name="err_can_not_create_record_file">Impossible de créer le fichier d\'enregistrement !</string>
|
||||||
|
<string name="err_initialize_video_call_recording">Une erreur a survenue lors de l\'initialisation de l\'enregistrement vidéo !</string>
|
||||||
</resources>
|
</resources>
|
@ -338,4 +338,9 @@
|
|||||||
<string name="err_save_image_in_gallery">Could not save the image in the gallery!</string>
|
<string name="err_save_image_in_gallery">Could not save the image in the gallery!</string>
|
||||||
<string name="success_save_image_in_gallery">Successfully saved the image into the gallery!</string>
|
<string name="success_save_image_in_gallery">Successfully saved the image into the gallery!</string>
|
||||||
<string name="err_missing_call_config">Missing call configuration!</string>
|
<string name="err_missing_call_config">Missing call configuration!</string>
|
||||||
|
<string name="action_record_video">Record video</string>
|
||||||
|
<string name="action_stop_record_video">Stop video record</string>
|
||||||
|
<string name="err_call_no_peer_connected">This call is not connected to any peer!</string>
|
||||||
|
<string name="err_can_not_create_record_file">Can not create record file!</string>
|
||||||
|
<string name="err_initialize_video_call_recording">Could not initialize video call recording!</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user