Recherche avancée

Médias (2)

Mot : - Tags -/kml

Autres articles (69)

  • Participer à sa traduction

    10 avril 2011

    Vous pouvez nous aider à améliorer les locutions utilisées dans le logiciel ou à traduire celui-ci dans n’importe qu’elle nouvelle langue permettant sa diffusion à de nouvelles communautés linguistiques.
    Pour ce faire, on utilise l’interface de traduction de SPIP où l’ensemble des modules de langue de MediaSPIP sont à disposition. ll vous suffit de vous inscrire sur la liste de discussion des traducteurs pour demander plus d’informations.
    Actuellement MediaSPIP n’est disponible qu’en français et (...)

  • Librairies et logiciels spécifiques aux médias

    10 décembre 2010, par

    Pour un fonctionnement correct et optimal, plusieurs choses sont à prendre en considération.
    Il est important, après avoir installé apache2, mysql et php5, d’installer d’autres logiciels nécessaires dont les installations sont décrites dans les liens afférants. Un ensemble de librairies multimedias (x264, libtheora, libvpx) utilisées pour l’encodage et le décodage des vidéos et sons afin de supporter le plus grand nombre de fichiers possibles. Cf. : ce tutoriel ; FFMpeg avec le maximum de décodeurs et (...)

  • XMP PHP

    13 mai 2011, par

    Dixit Wikipedia, XMP signifie :
    Extensible Metadata Platform ou XMP est un format de métadonnées basé sur XML utilisé dans les applications PDF, de photographie et de graphisme. Il a été lancé par Adobe Systems en avril 2001 en étant intégré à la version 5.0 d’Adobe Acrobat.
    Étant basé sur XML, il gère un ensemble de tags dynamiques pour l’utilisation dans le cadre du Web sémantique.
    XMP permet d’enregistrer sous forme d’un document XML des informations relatives à un fichier : titre, auteur, historique (...)

Sur d’autres sites (9310)

  • checkasm : Serialize read_time() calls on x86

    8 octobre 2014, par Henrik Gramner
    checkasm : Serialize read_time() calls on x86
    

    Improves the accuracy of benchmarks, especially in short functions.

    To quote the Intel 64 and IA-32 Architectures Software Developer’s Manual :
    "The RDTSC instruction is not a serializing instruction. It does not necessarily
    wait until all previous instructions have been executed before reading the counter.
    Similarly, subsequent instructions may begin execution before the read operation
    is performed. If software requires RDTSC to be executed only after all previous
    instructions have completed locally, it can either use RDTSCP (if the processor
    supports that instruction) or execute the sequence LFENCE ;RDTSC."

    RDTSCP would accomplish the same task, but it’s only available since Nehalem.

    This change makes SSE2 a requirement to run checkasm.

    • [DH] tools/checkasm.c
  • FFmpeg Overwiting Playlist

    30 septembre 2024, par Program-Me-Rev

    I'm working on an implementation where I aim to generate a DASH Playlist from Raw Camera2 data in Android Java using FFmpeg

    


    However , the current implementation only produces Three .m4s files regardless of how long the recording lasts . My goal is to create a playlist with 1-second .m4s Segments , but the output only includes the following files , and the video length doesn't exceed 2 seconds :

    


    - playlist.mpd
- init.m4s
- 1.m4s
- 2.m4s


    


    While the temporary files are created as expected , the .m4s files stop after these two segments . Additionally , only the last 2 seconds of the recording are retained , no matter how long the recording runs

    


    The FFmpeg output indicates that FFmpeg is repeatedly overwriting the previously generated playlist , which may explain why the recording doesn't extend beyond 2 seconds

    


    FFmpeg version : 6.0

    


        package rev.ca.rev_media_dash_camera2;&#xA;&#xA;    import android.app.Activity;&#xA;    import android.content.Context;&#xA;    import android.media.Image;&#xA;    import android.util.Log;&#xA;    import android.util.Size;&#xA;&#xA;    import androidx.annotation.NonNull;&#xA;    import androidx.camera.core.CameraSelector;&#xA;    import androidx.camera.core.ImageAnalysis;&#xA;    import androidx.camera.core.ImageProxy;&#xA;    import androidx.camera.core.Preview;&#xA;    import androidx.camera.lifecycle.ProcessCameraProvider;&#xA;    import androidx.camera.view.PreviewView;&#xA;    import androidx.core.content.ContextCompat;&#xA;    import androidx.lifecycle.LifecycleOwner;&#xA;&#xA;    import com.arthenica.ffmpegkit.FFmpegKit;&#xA;    import com.arthenica.ffmpegkit.ReturnCode;&#xA;    import com.google.common.util.concurrent.ListenableFuture;&#xA;&#xA;    import java.io.File;&#xA;    import java.io.FileOutputStream;&#xA;    import java.io.IOException;&#xA;    import java.nio.ByteBuffer;&#xA;    import java.util.concurrent.ExecutionException;&#xA;    import java.util.concurrent.ExecutorService;&#xA;    import java.util.concurrent.Executors;&#xA;&#xA;    public class RevCameraCapture {&#xA;        private static final String REV_TAG = "RevCameraCapture";&#xA;&#xA;        private final Context revContext;&#xA;        private final ExecutorService revExecutorService;&#xA;        private final String revOutDirPath = "/storage/emulated/0/Documents/Owki/rev_web_rtc_live_chat_temp_files/_abc_rev_uploads_temp";&#xA;        private boolean isRevRecording;&#xA;        private File revTempFile;&#xA;        private int revFrameCount = 0; // Counter for frames captured&#xA;&#xA;        public RevCameraCapture(Context revContext) {&#xA;            this.revContext = revContext;&#xA;&#xA;            revInitDir(revOutDirPath);&#xA;            revCheckOrCreatePlaylist();&#xA;&#xA;            revExecutorService = Executors.newSingleThreadExecutor();&#xA;        }&#xA;&#xA;        private void revInitDir(String revDirPath) {&#xA;            // Create a File object for the directory&#xA;            File revNestedDir = new File(revDirPath);&#xA;&#xA;            // Check if the directory exists, if not, create it&#xA;            if (!revNestedDir.exists()) {&#xA;                boolean revResult = revNestedDir.mkdirs();  // mkdirs() creates the whole path&#xA;                if (revResult) {&#xA;                    Log.e(REV_TAG, ">>> Directories created successfully.");&#xA;                } else {&#xA;                    Log.e(REV_TAG, ">>> Failed to create directories.");&#xA;                }&#xA;            } else {&#xA;                Log.e(REV_TAG, ">>> Directories already exist.");&#xA;            }&#xA;        }&#xA;&#xA;        private void revCheckOrCreatePlaylist() {&#xA;            File revPlaylistFile = new File(revOutDirPath, "rev_playlist.mpd");&#xA;            if (!revPlaylistFile.exists()) {&#xA;                // Create an empty playlist if it doesn&#x27;t exist&#xA;                try {&#xA;                    FileOutputStream revFos = new FileOutputStream(revPlaylistFile);&#xA;                    revFos.write("".getBytes());&#xA;                    revFos.close();&#xA;                } catch (IOException e) {&#xA;                    Log.e(REV_TAG, ">>> Error creating initial rev_playlist : ", e);&#xA;                }&#xA;            }&#xA;        }&#xA;&#xA;&#xA;        private void revStartFFmpegProcess() {&#xA;            // Ensure revTempFile exists before processing&#xA;            if (revTempFile == null || !revTempFile.exists()) {&#xA;                Log.e(REV_TAG, ">>> Temporary file does not exist for FFmpeg processing.");&#xA;                return;&#xA;            }&#xA;&#xA;            // FFmpeg command to convert the temp file to DASH format and append to the existing rev_playlist&#xA;            String ffmpegCommand = "-f rawvideo -pixel_format yuv420p -video_size 704x704 " &#x2B; "-i " &#x2B; revTempFile.getAbsolutePath() &#x2B; " -c:v mpeg4 -b:v 1M " &#x2B; "-f dash -seg_duration 1 -use_template 1 -use_timeline 1 " &#x2B; "-init_seg_name &#x27;init.m4s&#x27; -media_seg_name &#x27;$Number$.m4s&#x27; " &#x2B; revOutDirPath &#x2B; "/rev_playlist.mpd -loglevel debug";&#xA;&#xA;&#xA;            FFmpegKit.executeAsync(ffmpegCommand, session -> {&#xA;                ReturnCode returnCode = session.getReturnCode();&#xA;                if (ReturnCode.isSuccess(returnCode)) {&#xA;                    // Optionally handle success, e.g., log or notify that the process completed successfully&#xA;                } else {&#xA;                    Log.e(REV_TAG, ">>> FFmpeg process failed with return code : " &#x2B; returnCode);&#xA;                }&#xA;            });&#xA;        }&#xA;&#xA;&#xA;        public void revStartCamera() {&#xA;            isRevRecording = true;&#xA;&#xA;            ListenableFuture<processcameraprovider> revCameraProviderFuture = ProcessCameraProvider.getInstance(revContext);&#xA;&#xA;            revCameraProviderFuture.addListener(() -> {&#xA;                try {&#xA;                    ProcessCameraProvider revCameraProvider = revCameraProviderFuture.get();&#xA;                    revBindPreview(revCameraProvider);&#xA;                    revBindImageAnalysis(revCameraProvider);&#xA;                } catch (ExecutionException | InterruptedException e) {&#xA;                    Log.e(REV_TAG, ">>> Failed to start camera : ", e);&#xA;                }&#xA;            }, ContextCompat.getMainExecutor(revContext));&#xA;        }&#xA;&#xA;        private void revBindPreview(ProcessCameraProvider revCameraProvider) {&#xA;            CameraSelector revCameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();&#xA;&#xA;            PreviewView previewView = ((Activity) revContext).findViewById(R.id.previewView);&#xA;            Preview preview = new Preview.Builder().build();&#xA;            preview.setSurfaceProvider(previewView.getSurfaceProvider());&#xA;&#xA;            revCameraProvider.unbindAll();&#xA;            revCameraProvider.bindToLifecycle((LifecycleOwner) revContext, revCameraSelector, preview);&#xA;        }&#xA;&#xA;        private void revBindImageAnalysis(@NonNull ProcessCameraProvider revCameraProvider) {&#xA;            ImageAnalysis revImageAnalysis = new ImageAnalysis.Builder().setTargetResolution(new Size(640, 480)) // Lower the resolution to reduce memory consumption&#xA;                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();&#xA;&#xA;            revImageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(revContext), this::revAnalyze);&#xA;            CameraSelector revCameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();&#xA;&#xA;            revCameraProvider.bindToLifecycle((LifecycleOwner) revContext, revCameraSelector, revImageAnalysis);&#xA;        }&#xA;&#xA;        @androidx.annotation.OptIn(markerClass = androidx.camera.core.ExperimentalGetImage.class)&#xA;        private void revAnalyze(@NonNull ImageProxy revImageProxy) {&#xA;            try {&#xA;                revProcessImageFrame(revImageProxy);&#xA;            } catch (Exception e) {&#xA;                Log.e(REV_TAG, ">>> Error processing revImage frame", e);&#xA;            } finally {&#xA;                revImageProxy.close(); // Always close the revImageProxy&#xA;            }&#xA;        }&#xA;&#xA;        @androidx.annotation.OptIn(markerClass = androidx.camera.core.ExperimentalGetImage.class)&#xA;        private void revProcessImageFrame(@NonNull ImageProxy revImageProxy) {&#xA;            Image revImage = revImageProxy.getImage();&#xA;            if (revImage != null) {&#xA;                byte[] revImageBytes = revConvertYUV420888ToByteArray(revImage);&#xA;                revWriteFrameToTempFile(revImageBytes); // Write frame to a temporary file&#xA;            }&#xA;            revImageProxy.close(); // Close the ImageProxy to release the revImage buffer&#xA;        }&#xA;&#xA;        private byte[] revConvertYUV420888ToByteArray(Image revImage) {&#xA;            Image.Plane[] planes = revImage.getPlanes();&#xA;            ByteBuffer revBufferY = planes[0].getBuffer();&#xA;            ByteBuffer revBufferU = planes[1].getBuffer();&#xA;            ByteBuffer revBufferV = planes[2].getBuffer();&#xA;&#xA;            int revWidth = revImage.getWidth();&#xA;            int revHeight = revImage.getHeight();&#xA;&#xA;            int revSizeY = revWidth * revHeight;&#xA;            int revSizeUV = (revWidth / 2) * (revHeight / 2); // U and V sizes are half the Y size&#xA;&#xA;            // Total size = Y &#x2B; U &#x2B; V&#xA;            byte[] revData = new byte[revSizeY &#x2B; 2 * revSizeUV];&#xA;&#xA;            // Copy Y plane&#xA;            revBufferY.get(revData, 0, revSizeY);&#xA;&#xA;            // Copy U and V planes, accounting for row stride and pixel stride&#xA;            int revOffset = revSizeY;&#xA;            int revPixelStrideU = planes[1].getPixelStride();&#xA;            int rowStrideU = planes[1].getRowStride();&#xA;            int revPixelStrideV = planes[2].getPixelStride();&#xA;            int rowStrideV = planes[2].getRowStride();&#xA;&#xA;            // Copy U plane&#xA;            for (int row = 0; row &lt; revHeight / 2; row&#x2B;&#x2B;) {&#xA;                for (int col = 0; col &lt; revWidth / 2; col&#x2B;&#x2B;) {&#xA;                    revData[revOffset&#x2B;&#x2B;] = revBufferU.get(row * rowStrideU &#x2B; col * revPixelStrideU);&#xA;                }&#xA;            }&#xA;&#xA;            // Copy V plane&#xA;            for (int row = 0; row &lt; revHeight / 2; row&#x2B;&#x2B;) {&#xA;                for (int col = 0; col &lt; revWidth / 2; col&#x2B;&#x2B;) {&#xA;                    revData[revOffset&#x2B;&#x2B;] = revBufferV.get(row * rowStrideV &#x2B; col * revPixelStrideV);&#xA;                }&#xA;            }&#xA;&#xA;            return revData;&#xA;        }&#xA;&#xA;&#xA;        private void revWriteFrameToTempFile(byte[] revImageBytes) {&#xA;            revExecutorService.execute(() -> {&#xA;                try {&#xA;                    // Create a new temp file for each segment if needed&#xA;                    if (revTempFile == null || revFrameCount == 0) {&#xA;                        revTempFile = File.createTempFile("vid_segment_", ".yuv", new File(revOutDirPath));&#xA;                    }&#xA;&#xA;                    try (FileOutputStream revFos = new FileOutputStream(revTempFile, true)) {&#xA;                        revFos.write(revImageBytes);&#xA;                    }&#xA;&#xA;                    revFrameCount&#x2B;&#x2B;;&#xA;&#xA;                    // Process after 60 frames (2 second for 30 fps)&#xA;                    if (revFrameCount >= 60 &amp;&amp; isRevRecording) {&#xA;                        revStartFFmpegProcess();  // Process the segment with FFmpeg&#xA;                        revFrameCount = 0;  // Reset the frame count&#xA;                        revTempFile = null;  // Reset temp file for the next segment&#xA;                    }&#xA;&#xA;                } catch (IOException e) {&#xA;                    Log.e(REV_TAG, ">>> Error writing frame to temp file : ", e);&#xA;                }&#xA;            });&#xA;        }&#xA;&#xA;        public void revStopCamera() {&#xA;            isRevRecording = false;&#xA;            if (revTempFile != null &amp;&amp; revTempFile.exists()) {&#xA;                revTempFile.delete(); // Clean up the temporary file&#xA;                revTempFile = null; // Reset the temp file reference&#xA;            }&#xA;        }&#xA;    }&#xA;&#xA;&#xA;    package rev.ca.rev_media_dash_camera2;&#xA;&#xA;    import android.os.Bundle;&#xA;&#xA;    import androidx.appcompat.app.AppCompatActivity;&#xA;&#xA;    public class MainActivity extends AppCompatActivity {&#xA;        private RevCameraCapture revCameraCapture;&#xA;&#xA;        @Override&#xA;        protected void onCreate(Bundle savedInstanceState) {&#xA;            super.onCreate(savedInstanceState);&#xA;            setContentView(R.layout.activity_main);&#xA;&#xA;            revCameraCapture = new RevCameraCapture(this);&#xA;        }&#xA;&#xA;        @Override&#xA;        protected void onStart() {&#xA;            super.onStart();&#xA;            try {&#xA;                revCameraCapture.revStartCamera();&#xA;            } catch (Exception e) {&#xA;                e.printStackTrace();&#xA;            }&#xA;        }&#xA;&#xA;        @Override&#xA;        protected void onStop() {&#xA;            super.onStop();&#xA;            revCameraCapture.revStopCamera(); // Ensure camera is stopped when not in use&#xA;        }&#xA;    }&#xA;</processcameraprovider>

    &#xA;

  • record mediasoup RTP stream using FFmpeg for Firefox

    30 juillet 2024, par Hadi Aghandeh

    I am trying to record WebRTC stream using mediasoup. I could record successfully on chrome and safari 13/14/15. However on Firefox the does not work.

    &#xA;

    Client side code is a vue js component which gets rtp-compabilities using socket.io and create producers after the server creates the transports. This works good on chrome and safari.

    &#xA;

    const { connect , createLocalTracks } = require(&#x27;twilio-video&#x27;);&#xA;const SocketClient = require("socket.io-client");&#xA;const SocketPromise = require("socket.io-promise").default;&#xA;const MediasoupClient = require("mediasoup-client");&#xA;&#xA;export default {&#xA;    data() {&#xA;        return {&#xA;            errors: [],&#xA;            isReady: false,&#xA;            isRecording: false,&#xA;            loading: false,&#xA;            sapio: {&#xA;                token: null,&#xA;                connectionId: 0&#xA;            },&#xA;            server: {&#xA;                host: &#x27;https://rtc.test&#x27;,&#xA;                ws: &#x27;/server&#x27;,&#xA;                socket: null,&#xA;            },&#xA;            peer: {},&#xA;        }&#xA;    },&#xA;    mounted() {&#xA;        this.init();&#xA;    },&#xA;    methods: {&#xA;        async init() {&#xA;            await this.startCamera();&#xA;&#xA;            if (this.takeId) {&#xA;                await this.recordBySapioServer();&#xA;            }&#xA;        },&#xA;        startCamera() {&#xA;            return new Promise( (resolve, reject) => {&#xA;                if (window.videoMediaStreamObject) {&#xA;                    this.setVideoElementStream(window.videoMediaStreamObject);&#xA;                    resolve();&#xA;                } else {&#xA;                    // Get user media as required&#xA;                    try {&#xA;                        this.localeStream = navigator.mediaDevices.getUserMedia({&#xA;                            audio: true,&#xA;                            video: true,&#xA;                        }).then((stream) => {&#xA;                            this.setVideoElementStream(stream);&#xA;                            resolve();&#xA;                        })&#xA;                    } catch (err) {&#xA;                        console.error(err);&#xA;                        reject();&#xA;                    }&#xA;                }&#xA;            })&#xA;        },&#xA;        setVideoElementStream(stream) {&#xA;            this.localStream = stream;&#xA;            this.$refs.video.srcObject = stream;&#xA;            this.$refs.video.muted = true;&#xA;            this.$refs.video.play().then((video) => {&#xA;                this.isStreaming = true;&#xA;                this.height = this.$refs.video.videoHeight;&#xA;                this.width = this.$refs.video.videoWidth;&#xA;            });&#xA;        },&#xA;        // first thing we need is connecting to websocket&#xA;        connectToSocket() {&#xA;            const serverUrl = this.server.host;&#xA;            console.log("Connect with sapio rtc server:", serverUrl);&#xA;&#xA;            const socket = SocketClient(serverUrl, {&#xA;                path:  this.server.ws,&#xA;                transports: ["websocket"],&#xA;            });&#xA;            this.socket = socket;&#xA;&#xA;            socket.on("connect", () => {&#xA;                console.log("WebSocket connected");&#xA;                // we ask for rtp-capabilities from server to send to us&#xA;                socket.emit(&#x27;send-rtp-capabilities&#x27;);&#xA;            });&#xA;&#xA;            socket.on("error", (err) => {&#xA;                this.loading = true;&#xA;                console.error("WebSocket error:", err);&#xA;            });&#xA;&#xA;            socket.on("router-rtp-capabilities", async (msg) => {&#xA;                const { routerRtpCapabilities, sessionId, externalId } = msg;&#xA;                console.log(&#x27;[rtpCapabilities:%o]&#x27;, routerRtpCapabilities);&#xA;                this.routerRtpCapabilities = routerRtpCapabilities;&#xA;&#xA;                try {&#xA;                    const device = new MediasoupClient.Device();&#xA;                    // Load the mediasoup device with the router rtp capabilities gotten from the server&#xA;                    await device.load({ routerRtpCapabilities });&#xA;&#xA;                    this.peer.sessionId = sessionId;&#xA;                    this.peer.externalId = externalId;&#xA;                    this.peer.device = device;&#xA;&#xA;                    this.createTransport();&#xA;                } catch (error) {&#xA;                    console.error(&#x27;failed to init device [error:%o]&#x27;, error);&#xA;                    socket.disconnect();&#xA;                }&#xA;            });&#xA;&#xA;            socket.on("create-transport", async (msg) => {&#xA;                console.log(&#x27;handleCreateTransportRequest() [data:%o]&#x27;, msg);&#xA;&#xA;                try {&#xA;                    // Create the local mediasoup send transport&#xA;                    this.peer.sendTransport = await this.peer.device.createSendTransport(msg);&#xA;                    console.log(&#x27;send transport created [id:%s]&#x27;, this.peer.sendTransport.id);&#xA;&#xA;                    // Set the transport listeners and get the users media stream&#xA;                    this.handleSendTransportListeners();&#xA;                    this.setTracks();&#xA;                    this.loading = false;&#xA;                } catch (error) {&#xA;                    console.error(&#x27;failed to create transport [error:%o]&#x27;, error);&#xA;                    socket.disconnect();&#xA;                }&#xA;            });&#xA;&#xA;            socket.on("connect-transport", async (msg) => {&#xA;                console.log(&#x27;handleTransportConnectRequest()&#x27;);&#xA;                try {&#xA;                    const action = this.connectTransport;&#xA;&#xA;                    if (!action) {&#xA;                        throw new Error(&#x27;transport-connect action was not found&#x27;);&#xA;                    }&#xA;&#xA;                    await action(msg);&#xA;                } catch (error) {&#xA;                    console.error(&#x27;ailed [error:%o]&#x27;, error);&#xA;                }&#xA;            });&#xA;&#xA;            socket.on("produce", async (msg) => {&#xA;                console.log(&#x27;handleProduceRequest()&#x27;);&#xA;                try {&#xA;                    if (!this.produce) {&#xA;                        throw new Error(&#x27;produce action was not found&#x27;);&#xA;                    }&#xA;                    await this.produce(msg);&#xA;                } catch (error) {&#xA;                    console.error(&#x27;failed [error:%o]&#x27;, error);&#xA;                }&#xA;            });&#xA;&#xA;            socket.on("recording", async (msg) => {&#xA;                this.isRecording = true;&#xA;            });&#xA;&#xA;            socket.on("recording-error", async (msg) => {&#xA;                this.isRecording = false;&#xA;                console.error(msg);&#xA;            });&#xA;&#xA;            socket.on("recording-closed", async (msg) => {&#xA;                this.isRecording = false;&#xA;                console.warn(msg)&#xA;            });&#xA;&#xA;        },&#xA;        createTransport() {&#xA;            console.log(&#x27;createTransport()&#x27;);&#xA;&#xA;            if (!this.peer || !this.peer.device.loaded) {&#xA;                throw new Error(&#x27;Peer or device is not initialized&#x27;);&#xA;            }&#xA;&#xA;            // First we must create the mediasoup transport on the server side&#xA;            this.socket.emit(&#x27;create-transport&#x27;,{&#xA;                sessionId: this.peer.sessionId&#xA;            });&#xA;        },&#xA;        handleSendTransportListeners() {&#xA;            this.peer.sendTransport.on(&#x27;connect&#x27;, this.handleTransportConnectEvent);&#xA;            this.peer.sendTransport.on(&#x27;produce&#x27;, this.handleTransportProduceEvent);&#xA;            this.peer.sendTransport.on(&#x27;connectionstatechange&#x27;, connectionState => {&#xA;                console.log(&#x27;send transport connection state change [state:%s]&#x27;, connectionState);&#xA;            });&#xA;        },&#xA;        handleTransportConnectEvent({ dtlsParameters }, callback, errback) {&#xA;            console.log(&#x27;handleTransportConnectEvent()&#x27;);&#xA;            try {&#xA;                this.connectTransport = (msg) => {&#xA;                    console.log(&#x27;connect-transport action&#x27;);&#xA;                    callback();&#xA;                    this.connectTransport = null;&#xA;                };&#xA;&#xA;                this.socket.emit(&#x27;connect-transport&#x27;,{&#xA;                    sessionId: this.peer.sessionId,&#xA;                    transportId: this.peer.sendTransport.id,&#xA;                    dtlsParameters&#xA;                });&#xA;&#xA;            } catch (error) {&#xA;                console.error(&#x27;handleTransportConnectEvent() failed [error:%o]&#x27;, error);&#xA;                errback(error);&#xA;            }&#xA;        },&#xA;        handleTransportProduceEvent({ kind, rtpParameters }, callback, errback)  {&#xA;            console.log(&#x27;handleTransportProduceEvent()&#x27;);&#xA;            try {&#xA;                this.produce = jsonMessage => {&#xA;                    console.log(&#x27;handleTransportProduceEvent callback [data:%o]&#x27;, jsonMessage);&#xA;                    callback({ id: jsonMessage.id });&#xA;                    this.produce = null;&#xA;                };&#xA;&#xA;                this.socket.emit(&#x27;produce&#x27;, {&#xA;                    sessionId: this.peer.sessionId,&#xA;                    transportId: this.peer.sendTransport.id,&#xA;                    kind,&#xA;                    rtpParameters&#xA;                });&#xA;            } catch (error) {&#xA;                console.error(&#x27;handleTransportProduceEvent() failed [error:%o]&#x27;, error);&#xA;                errback(error);&#xA;            }&#xA;        },&#xA;        async recordBySapioServer() {&#xA;            this.loading = true;&#xA;            this.connectToSocket();&#xA;        },&#xA;        async setTracks() {&#xA;            // Start mediasoup-client&#x27;s WebRTC producers&#xA;            const audioTrack = this.localStream.getAudioTracks()[0];&#xA;            this.peer.audioProducer = await this.peer.sendTransport.produce({&#xA;                track: audioTrack,&#xA;                codecOptions :&#xA;                    {&#xA;                        opusStereo : 1,&#xA;                        opusDtx    : 1&#xA;                    }&#xA;            });&#xA;&#xA;&#xA;            let encodings;&#xA;            let codec;&#xA;            const codecOptions = {videoGoogleStartBitrate : 1000};&#xA;&#xA;            codec = this.peer.device.rtpCapabilities.codecs.find((c) => c.kind.toLowerCase() === &#x27;video&#x27;);&#xA;            if (codec.mimeType.toLowerCase() === &#x27;video/vp9&#x27;) {&#xA;                encodings = { scalabilityMode: &#x27;S3T3_KEY&#x27; };&#xA;            } else {&#xA;                encodings = [&#xA;                    { scaleResolutionDownBy: 4, maxBitrate: 500000 },&#xA;                    { scaleResolutionDownBy: 2, maxBitrate: 1000000 },&#xA;                    { scaleResolutionDownBy: 1, maxBitrate: 5000000 }&#xA;                ];&#xA;            }&#xA;            const videoTrack = this.localStream.getVideoTracks()[0];&#xA;            this.peer.videoProducer =await this.peer.sendTransport.produce({&#xA;                track: videoTrack,&#xA;                encodings,&#xA;                codecOptions,&#xA;                codec&#xA;            });&#xA;&#xA;        },&#xA;        startRecording() {&#xA;            this.Q.answer.recordingId = this.peer.externalId;&#xA;            this.socket.emit("start-record", {&#xA;                sessionId: this.peer.sessionId&#xA;            });&#xA;        },&#xA;        stopRecording() {&#xA;            this.socket.emit("stop-record" , {&#xA;                sessionId: this.peer.sessionId&#xA;            });&#xA;        },&#xA;    },&#xA;&#xA;}&#xA;&#xA;&#xA;&#xA;

    &#xA;

    console.log of my ffmpeg process :

    &#xA;

    // sdp string&#xA;[sdpString:v=0&#xA;  o=- 0 0 IN IP4 127.0.0.1&#xA;  s=FFmpeg&#xA;  c=IN IP4 127.0.0.1&#xA;  t=0 0&#xA;  m=video 25549 RTP/AVP 101 &#xA;  a=rtpmap:101 VP8/90000&#xA;  a=sendonly&#xA;  m=audio 26934 RTP/AVP 100 &#xA;  a=rtpmap:100 opus/48000/2&#xA;  a=sendonly&#xA;  ]&#xA;&#xA;// ffmpeg args&#xA;commandArgs:[&#xA;  &#x27;-loglevel&#x27;,&#xA;  &#x27;debug&#x27;,&#xA;  &#x27;-protocol_whitelist&#x27;,&#xA;  &#x27;pipe,udp,rtp&#x27;,&#xA;  &#x27;-fflags&#x27;,&#xA;  &#x27;&#x2B;genpts&#x27;,&#xA;  &#x27;-f&#x27;,&#xA;  &#x27;sdp&#x27;,&#xA;  &#x27;-i&#x27;,&#xA;  &#x27;pipe:0&#x27;,&#xA;  &#x27;-map&#x27;,&#xA;  &#x27;0:v:0&#x27;,&#xA;  &#x27;-c:v&#x27;,&#xA;  &#x27;copy&#x27;,&#xA;  &#x27;-map&#x27;,&#xA;  &#x27;0:a:0&#x27;,&#xA;  &#x27;-strict&#x27;,&#xA;  &#x27;-2&#x27;,&#xA;  &#x27;-c:a&#x27;,&#xA;  &#x27;copy&#x27;,&#xA;  &#x27;-f&#x27;,&#xA;  &#x27;webm&#x27;,&#xA;  &#x27;-flags&#x27;,&#xA;  &#x27;&#x2B;global_header&#x27;,&#xA;  &#x27;-y&#x27;,&#xA;  &#x27;storage/recordings/26e63cb3-4f81-499e-941a-c0bb7f7f52ce.webm&#x27;,&#xA;  [length]: 26&#xA;]&#xA;// ffmpeg log&#xA;ffmpeg::process::data [data:&#x27;ffmpeg version n4.4&#x27;]&#xA;ffmpeg::process::data [data:&#x27; Copyright (c) 2000-2021 the FFmpeg developers&#x27;]&#xA;ffmpeg::process::data [data:&#x27;\n&#x27;]&#xA;ffmpeg::process::data [data:&#x27;  built with gcc 11.1.0 (GCC)\n&#x27;]&#xA;ffmpeg::process::data [data:&#x27;  configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-amf --enable-avisynth --enable-cuda-llvm --enable-lto --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-librsvg --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-nvdec --enable-nvenc --enable-shared --enable-version3\n&#x27;]&#xA;ffmpeg::process::data [data:&#x27;  libavutil      56. 70.100 / 56. 70.100\n&#x27; &#x2B;&#xA;  &#x27;  libavcodec     58.134.100 / 58.134.100\n&#x27; &#x2B;&#xA;  &#x27;  libavformat    58. 76.100 / 58. 76.100\n&#x27; &#x2B;&#xA;  &#x27;  libavdevice    58. 13.100 / 58. 13.100\n&#x27; &#x2B;&#xA;  &#x27;  libavfilter     7.110.100 /  7.110.100\n&#x27; &#x2B;&#xA;  &#x27;  libswscale      5.  9.100 /  5.  9.100\n&#x27; &#x2B;&#xA;  &#x27;  libswresample   3.  9.100 /  3.  9.100\n&#x27; &#x2B;&#xA;  &#x27;  libpostproc    55.  9.100 / 55.  9.100\n&#x27; &#x2B;&#xA;  &#x27;Splitting the commandline.\n&#x27; &#x2B;&#xA;  "Reading option &#x27;-loglevel&#x27; ... matched as option &#x27;loglevel&#x27; (set logging level) with argument &#x27;debug&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-protocol_whitelist&#x27; ..."]&#xA;ffmpeg::process::data [data:" matched as AVOption &#x27;protocol_whitelist&#x27; with argument &#x27;pipe,udp,rtp&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-fflags&#x27; ..."]&#xA;ffmpeg::process::data [data:" matched as AVOption &#x27;fflags&#x27; with argument &#x27;&#x2B;genpts&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-f&#x27; ... matched as option &#x27;f&#x27; (force format) with argument &#x27;sdp&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-i&#x27; ... matched as input url with argument &#x27;pipe:0&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-map&#x27; ... matched as option &#x27;map&#x27; (set input stream mapping) with argument &#x27;0:v:0&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-c:v&#x27; ... matched as option &#x27;c&#x27; (codec name) with argument &#x27;copy&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-map&#x27; ... matched as option &#x27;map&#x27; (set input stream mapping) with argument &#x27;0:a:0&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-strict&#x27; ...Routing option strict to both codec and muxer layer\n" &#x2B;&#xA;  " matched as AVOption &#x27;strict&#x27; with argument &#x27;-2&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-c:a&#x27; ... matched as option &#x27;c&#x27; (codec name) with argument &#x27;copy&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-f&#x27; ... matched as option &#x27;f&#x27; (force format) with argument &#x27;webm&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-flags&#x27; ... matched as AVOption &#x27;flags&#x27; with argument &#x27;&#x2B;global_header&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;-y&#x27; ... matched as option &#x27;y&#x27; (overwrite output files) with argument &#x27;1&#x27;.\n" &#x2B;&#xA;  "Reading option &#x27;storage/recordings/26e63cb3-4f81-499e-941a-c0bb7f7f52ce.webm&#x27; ... matched as output url.\n" &#x2B;&#xA;  &#x27;Finished splitting the commandline.\n&#x27; &#x2B;&#xA;  &#x27;Parsing a group of options: global .\n&#x27; &#x2B;&#xA;  &#x27;Applying option loglevel (set logging level) with argument debug.\n&#x27; &#x2B;&#xA;  &#x27;Applying option y (overwrite output files) with argument 1.\n&#x27; &#x2B;&#xA;  &#x27;Successfully parsed a group of options.\n&#x27; &#x2B;&#xA;  &#x27;Parsing a group of options: input url pipe:0.\n&#x27; &#x2B;&#xA;  &#x27;Applying option f (force format) with argument sdp.\n&#x27; &#x2B;&#xA;  &#x27;Successfully parsed a group of options.\n&#x27; &#x2B;&#xA;  &#x27;Opening an input file: pipe:0.\n&#x27; &#x2B;&#xA;  "[sdp @ 0x55604dc58400] Opening &#x27;pipe:0&#x27; for reading\n" &#x2B;&#xA;  &#x27;[sdp @ 0x55604dc58400] video codec set to: vp8\n&#x27; &#x2B;&#xA;  &#x27;[sdp @ 0x55604dc58400] audio codec set to: opus\n&#x27; &#x2B;&#xA;  &#x27;[sdp @ 0x55604dc58400] audio samplerate set to: 48000\n&#x27; &#x2B;&#xA;  &#x27;[sdp @ 0x55604dc58400] audio channels set to: 2\n&#x27; &#x2B;&#xA;  &#x27;[udp @ 0x55604dc6c500] end receive buffer size reported is 425984\n&#x27; &#x2B;&#xA;  &#x27;[udp @ 0x55604dc6c7c0] end receive buffer size reported is 425984\n&#x27; &#x2B;&#xA;  &#x27;[sdp @ 0x55604dc58400] setting jitter buffer size to 500\n&#x27; &#x2B;&#xA;  &#x27;[udp @ 0x55604dc6d900] end receive buffer size reported is 425984\n&#x27; &#x2B;&#xA;  &#x27;[udp @ 0x55604dc6d2c0] end receive buffer size reported is 425984\n&#x27; &#x2B;&#xA;  &#x27;[sdp @ 0x55604dc58400] setting jitter buffer size to 500\n&#x27;]&#xA;ffmpeg::process::data [data:&#x27;[sdp @ 0x55604dc58400] Before avformat_find_stream_info() pos: 210 bytes read:210 seeks:0 nb_streams:2\n&#x27;]&#xA;  **mediasoup:Consumer resume() &#x2B;1s**&#xA;  **mediasoup:Channel request() [method:consumer.resume, id:12] &#x2B;1s**&#xA;  **mediasoup:Channel request succeeded [method:consumer.resume, id:12] &#x2B;0ms**&#xA;  **mediasoup:Consumer resume() &#x2B;1ms**&#xA;  **mediasoup:Channel request() [method:consumer.resume, id:13] &#x2B;0ms**&#xA;  **mediasoup:Channel request succeeded [method:consumer.resume, id:13] &#x2B;0ms**&#xA;ffmpeg::process::data [data:&#x27;[sdp @ 0x55604dc58400] Could not find codec parameters for stream 0 (Video: vp8, 1 reference frame, yuv420p): unspecified size\n&#x27; &#x2B;&#xA;  "Consider increasing the value for the &#x27;analyzeduration&#x27; (0) and &#x27;probesize&#x27; (5000000) options\n"]&#xA;ffmpeg::process::data [data:&#x27;[sdp @ 0x55604dc58400] After avformat_find_stream_info() pos: 210 bytes read:210 seeks:0 frames:0\n&#x27; &#x2B;&#xA;  "Input #0, sdp, from &#x27;pipe:0&#x27;:\n" &#x2B;&#xA;  &#x27;  Metadata:\n&#x27; &#x2B;&#xA;  &#x27;    title           : FFmpeg\n&#x27; &#x2B;&#xA;  &#x27;  Duration: N/A, bitrate: N/A\n&#x27; &#x2B;&#xA;  &#x27;  Stream #0:0, 0, 1/90000: Video: vp8, 1 reference frame, yuv420p, 90k tbr, 90k tbn, 90k tbc\n&#x27; &#x2B;&#xA;  &#x27;  Stream #0:1, 0, 1/48000: Audio: opus, 48000 Hz, stereo, fltp\n&#x27; &#x2B;&#xA;  &#x27;Successfully opened the file.\n&#x27; &#x2B;&#xA;  &#x27;Parsing a group of options: output url storage/recordings/26e63cb3-4f81-499e-941a-c0bb7f7f52ce.webm.\n&#x27; &#x2B;&#xA;  &#x27;Applying option map (set input stream mapping) with argument 0:v:0.\n&#x27; &#x2B;&#xA;  &#x27;Applying option c:v (codec name) with argument copy.\n&#x27; &#x2B;&#xA;  &#x27;Applying option map (set input stream mapping) with argument 0:a:0.\n&#x27; &#x2B;&#xA;  &#x27;Applying option c:a (codec name) with argument copy.\n&#x27; &#x2B;&#xA;  &#x27;Applying option f (force format) with argument webm.\n&#x27; &#x2B;&#xA;  &#x27;Successfully parsed a group of options.\n&#x27; &#x2B;&#xA;  &#x27;Opening an output file: storage/recordings/26e63cb3-4f81-499e-941a-c0bb7f7f52ce.webm.\n&#x27; &#x2B;&#xA;  "[file @ 0x55604dce5bc0] Setting default whitelist &#x27;file,crypto,data&#x27;\n"]&#xA;ffmpeg::process::data [data:&#x27;Successfully opened the file.\n&#x27; &#x2B;&#xA;  &#x27;[webm @ 0x55604dce0fc0] dimensions not set\n&#x27; &#x2B;&#xA;  &#x27;Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument\n&#x27; &#x2B;&#xA;  &#x27;Error initializing output stream 0:1 -- \n&#x27; &#x2B;&#xA;  &#x27;Stream mapping:\n&#x27; &#x2B;&#xA;  &#x27;  Stream #0:0 -> #0:0 (copy)\n&#x27; &#x2B;&#xA;  &#x27;  Stream #0:1 -> #0:1 (copy)\n&#x27; &#x2B;&#xA;  &#x27;    Last message repeated 1 times\n&#x27; &#x2B;&#xA;  &#x27;[AVIOContext @ 0x55604dc6dcc0] Statistics: 0 seeks, 0 writeouts\n&#x27; &#x2B;&#xA;  &#x27;[AVIOContext @ 0x55604dc69380] Statistics: 210 bytes read, 0 seeks\n&#x27;]&#xA;ffmpeg::process::close&#xA;&#xA;

    &#xA;

    FFmpeg says dimensions not  set and Could not write header for output file when I use Firefox. This might be enough for understanding the problem, but if you need more information you can read how server side is performing.&#xA;Server-Side in summary can be something like this :&#xA;lets say we initialized worker and router at run time using following functions.

    &#xA;

        // Start the mediasoup workers&#xA;module.exports.initializeWorkers = async () => {&#xA;  const { logLevel, logTags, rtcMinPort, rtcMaxPort } = config.worker;&#xA;&#xA;  console.log(&#x27;initializeWorkers() creating %d mediasoup workers&#x27;, config.numWorkers);&#xA;&#xA;  for (let i = 0; i &lt; config.numWorkers; &#x2B;&#x2B;i) {&#xA;    const worker = await mediasoup.createWorker({&#xA;      logLevel, logTags, rtcMinPort, rtcMaxPort&#xA;    });&#xA;&#xA;    worker.once(&#x27;died&#x27;, () => {&#xA;      console.error(&#x27;worker::died worker has died exiting in 2 seconds... [pid:%d]&#x27;, worker.pid);&#xA;      setTimeout(() => process.exit(1), 2000);&#xA;    });&#xA;&#xA;    workers.push(worker);&#xA;  }&#xA;};&#xA;

    &#xA;

    module.exports.createRouter = async () => {&#xA;  const worker = getNextWorker();&#xA;&#xA;  console.log(&#x27;createRouter() creating new router [worker.pid:%d]&#x27;, worker.pid);&#xA;&#xA;  console.log(`config.router.mediaCodecs:${JSON.stringify(config.router.mediaCodecs)}`)&#xA;&#xA;  return await worker.createRouter({ mediaCodecs: config.router.mediaCodecs });&#xA;};&#xA;

    &#xA;

    We pass router.rtpCompatibilities to the client. clients get the rtpCompatibilities and create a device and loads it. after that a transport must be created at server side.

    &#xA;

        const handleCreateTransportRequest = async (jsonMessage) => {&#xA;&#xA;  const transport = await createTransport(&#x27;webRtc&#x27;, router);&#xA;&#xA;  var peer;&#xA;  try {peer = peers.get(jsonMessage.sessionId);}&#xA;  catch{console.log(&#x27;peer not found&#x27;)}&#xA;  &#xA;  peer.addTransport(transport);&#xA;&#xA;  peer.socket.emit(&#x27;create-transport&#x27;,{&#xA;    id: transport.id,&#xA;    iceParameters: transport.iceParameters,&#xA;    iceCandidates: transport.iceCandidates,&#xA;    dtlsParameters: transport.dtlsParameters&#xA;  });&#xA;};&#xA;

    &#xA;

    Then after the client side also created the transport we listen to connect event an at the time of event, we request the server to create connection.

    &#xA;

    const handleTransportConnectRequest = async (jsonMessage) => {&#xA;  var peer;&#xA;  try {peer = peers.get(jsonMessage.sessionId);}&#xA;  catch{console.log(&#x27;peer not found&#x27;)}&#xA;&#xA;  if (!peer) {&#xA;    throw new Error(`Peer with id ${jsonMessage.sessionId} was not found`);&#xA;  }&#xA;&#xA;  const transport = peer.getTransport(jsonMessage.transportId);&#xA;&#xA;  if (!transport) {&#xA;    throw new Error(`Transport with id ${jsonMessage.transportId} was not found`);&#xA;  }&#xA;&#xA;  await transport.connect({ dtlsParameters: jsonMessage.dtlsParameters });&#xA;  console.log(&#x27;handleTransportConnectRequest() transport connected&#x27;);&#xA;  peer.socket.emit(&#x27;connect-transport&#x27;);&#xA;};&#xA;

    &#xA;

    Similar thing happen on produce event.

    &#xA;

    const handleProduceRequest = async (jsonMessage) => {&#xA;  console.log(&#x27;handleProduceRequest [data:%o]&#x27;, jsonMessage);&#xA;&#xA;  var peer;&#xA;  try {peer = peers.get(jsonMessage.sessionId);}&#xA;  catch{console.log(&#x27;peer not found&#x27;)}&#xA;&#xA;  if (!peer) {&#xA;    throw new Error(`Peer with id ${jsonMessage.sessionId} was not found`);&#xA;  }&#xA;&#xA;  const transport = peer.getTransport(jsonMessage.transportId);&#xA;&#xA;  if (!transport) {&#xA;    throw new Error(`Transport with id ${jsonMessage.transportId} was not found`);&#xA;  }&#xA;&#xA;  const producer = await transport.produce({&#xA;    kind: jsonMessage.kind,&#xA;    rtpParameters: jsonMessage.rtpParameters&#xA;  });&#xA;&#xA;  peer.addProducer(producer);&#xA;&#xA;  console.log(&#x27;handleProducerRequest() new producer added [id:%s, kind:%s]&#x27;, producer.id, producer.kind);&#xA;&#xA;  peer.socket.emit(&#x27;produce&#x27;,{&#xA;    id: producer.id,&#xA;    kind: producer.kind&#xA;  });&#xA;};&#xA;

    &#xA;

    For Recording, first I create plain transports for audio and video producers.

    &#xA;

    const rtpTransport = router.createPlainTransport(config.plainRtpTransport);&#xA;

    &#xA;

    then rtp transport must be connected to ports :

    &#xA;

      await rtpTransport.connect({&#xA;    ip: &#x27;127.0.0.1&#x27;,&#xA;    port: remoteRtpPort,&#xA;    rtcpPort: remoteRtcpPort&#xA;  });&#xA;

    &#xA;

    Then the consumer must also be created.

    &#xA;

      const rtpConsumer = await rtpTransport.consume({&#xA;    producerId: producer.id,&#xA;    rtpCapabilities,&#xA;    paused: true&#xA;  });&#xA;

    &#xA;

    After that we can start recording using following code :

    &#xA;

     this._rtpParameters = args;&#xA;    this._process = undefined;&#xA;    this._observer = new EventEmitter();&#xA;    this._peer = args.peer;&#xA;&#xA;    this._sdpString = createSdpText(this._rtpParameters);&#xA;    this._sdpStream = convertStringToStream(this._sdpString);&#xA;    // create dir&#xA;    const dir = process.env.REOCRDING_PATH ?? &#x27;storage/recordings&#x27;;&#xA;    if (!fs.existsSync(dir)) shelljs.mkdir(&#x27;-p&#x27;, dir);&#xA;  &#xA;    this._extension = &#x27;webm&#x27;;&#xA;    // create file path&#xA;    this._path = `${dir}/${args.peer.sessionId}.${this._extension}`&#xA;    let loop = 0;&#xA;    while(fs.existsSync(this._path)) {&#xA;      this._path = `${dir}/${args.peer.sessionId}-${&#x2B;&#x2B;loop}.${this._extension}`&#xA;    }&#xA;&#xA;this._recordingnModel = await Recording.findOne({sessionIds: { $in: [this._peer.sessionId] }})&#xA;    this._recordingnModel.files.push(this._path);&#xA;    this._recordingnModel.save();&#xA;&#xA;let proc  = ffmpeg(this._sdpStream)&#xA;    .inputOptions([&#xA;      &#x27;-protocol_whitelist&#x27;,&#x27;pipe,udp,rtp&#x27;,&#xA;      &#x27;-f&#x27;,&#x27;sdp&#x27;,&#xA;    ])&#xA;    .format(this._extension)&#xA;    .output(this._path)&#xA;    .size(&#x27;720x?&#x27;)&#xA;    .on(&#x27;start&#x27;, ()=>{&#xA;      this._peer.socket.emit(&#x27;recording&#x27;);&#xA;    })&#xA;    .on(&#x27;end&#x27;, ()=>{&#xA;      let path = this._path.replace(&#x27;storage/recordings/&#x27;, &#x27;&#x27;);&#xA;      this._peer.socket.emit(&#x27;recording-closed&#x27;, {&#xA;        url: `${process.env.APP_URL}/recording/file/${path}`&#xA;      });&#xA;    });&#xA;&#xA;    proc.run();&#xA;    this._process =  proc;&#xA;  }&#xA;&#xA;

    &#xA;