mirror of
https://github.com/pierre42100/ComunicAndroid
synced 2024-12-25 13:08:58 +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
|
||||
* @return Generated string
|
||||
@ -68,6 +68,18 @@ public class StringsUtils {
|
||||
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
|
||||
* size
|
||||
|
@ -137,4 +137,21 @@ public final class Constants {
|
||||
public static final String PREFERENCE_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.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
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.CallsConfiguration;
|
||||
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.asynctasks.GetCallInformationTask;
|
||||
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.models.CallPeerConnection;
|
||||
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.UiUtils;
|
||||
import org.communiquons.signalexchangerclient.SignalExchangerCallback;
|
||||
@ -43,11 +47,16 @@ import org.webrtc.SessionDescription;
|
||||
import org.webrtc.StatsReport;
|
||||
import org.webrtc.SurfaceViewRenderer;
|
||||
import org.webrtc.VideoCapturer;
|
||||
import org.webrtc.VideoFileRenderer;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoSink;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -96,6 +105,13 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
private boolean mIsCameraStopped = false;
|
||||
private boolean mIsMicrophoneStopped = false;
|
||||
|
||||
|
||||
/**
|
||||
* Specify whether we are recording video or not
|
||||
*/
|
||||
private boolean mIsRecordingVideo = false;
|
||||
private VideoFileRenderer mVideoFileRenderer;
|
||||
|
||||
/**
|
||||
* Connections list
|
||||
*/
|
||||
@ -119,6 +135,7 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
private View mButtonsView;
|
||||
private ImageButton mStopCameraButton;
|
||||
private ImageButton mStopMicrophoneButton;
|
||||
private ImageButton mMoreActionsButton;
|
||||
|
||||
|
||||
@Override
|
||||
@ -203,6 +220,9 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
|
||||
mStopMicrophoneButton = findViewById(R.id.stopMicrophoneButton);
|
||||
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);
|
||||
|
||||
EglBase eglBase = EglBase.create();
|
||||
callPeer.setEglRenderer(eglBase);
|
||||
|
||||
//Create peer connection
|
||||
PeerConnectionClient peerConnectionClient = new PeerConnectionClient(
|
||||
@ -403,6 +424,12 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
callPeer.getRemoteSinks().add(remoteProxyRenderer);
|
||||
callPeer.setRemoteProxyRenderer(remoteProxyRenderer);
|
||||
|
||||
|
||||
ProxyVideoSink recordProxyVideoSink = new ProxyVideoSink();
|
||||
callPeer.getRemoteSinks().add(recordProxyVideoSink);
|
||||
callPeer.setRecordProxyRenderer(recordProxyVideoSink);
|
||||
|
||||
|
||||
//Start connection
|
||||
peerConnectionClient.createPeerConnection(
|
||||
mLocalProxyVideoSink,
|
||||
@ -423,6 +450,9 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
mHangUpButton.setVisibility(View.GONE);
|
||||
mStopped = true;
|
||||
|
||||
//Stop recording video
|
||||
stopVideoRecord();
|
||||
|
||||
if(mRefreshCallInformation != null)
|
||||
mRefreshCallInformation.interrupt();
|
||||
|
||||
@ -449,6 +479,11 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
if(callPeer == null)
|
||||
return;
|
||||
|
||||
//Check if it is the first peer, if yes stop recording
|
||||
if(mList.get(0) == callPeer)
|
||||
stopVideoRecord();
|
||||
|
||||
|
||||
((ProxyVideoSink)callPeer.getRemoteProxyRenderer()).setTarget(null);
|
||||
|
||||
callPeer.getPeerConnectionClient().close();
|
||||
@ -500,6 +535,87 @@ public class CallActivity extends BaseActivity implements SignalExchangerCallbac
|
||||
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
|
||||
@Nullable
|
||||
|
@ -2,6 +2,7 @@ package org.communiquons.android.comunic.client.ui.models;
|
||||
|
||||
import org.appspot.apprtc.PeerConnectionClient;
|
||||
import org.communiquons.android.comunic.client.data.models.CallMember;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.SurfaceViewRenderer;
|
||||
import org.webrtc.VideoSink;
|
||||
|
||||
@ -19,7 +20,9 @@ public class CallPeerConnection {
|
||||
private PeerConnectionClient peerConnectionClient;
|
||||
private boolean connected = false;
|
||||
private VideoSink remoteProxyRenderer;
|
||||
private VideoSink recordProxyRenderer;
|
||||
private ArrayList<VideoSink> remoteSinks = new ArrayList<>();
|
||||
private EglBase eglRenderer;
|
||||
|
||||
//Views
|
||||
private SurfaceViewRenderer mRemoteViewView;
|
||||
@ -75,4 +78,20 @@ public class CallPeerConnection {
|
||||
public void setRemoteProxyRenderer(VideoSink 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: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>
|
||||
|
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="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="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>
|
@ -338,4 +338,9 @@
|
||||
<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="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>
|
||||
|
Loading…
Reference in New Issue
Block a user