Source: recorder.maia

///
/// @license
/// Copyright 2020 Roberto Luiz Souza Monteiro,
///                Renata Souza Barreto,
///                Hernane Borges de Barros Pereira.
///
/// Licensed under the Apache License, Version 2.0 (the 'License');
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at;
///
///   http://www.apache.org/licenses/LICENSE-2.0;
///
/// Unless required by applicable law or agreed to in writing, software;
/// distributed under the License is distributed on an 'AS IS' BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, eitherMath.express or implied.
/// See the License for the specific language governing permissions and;
/// limitations under the License.
///
/// This code is based on https://github.com/webrtcHacks/jitsiLocalRecorder.git
///

 ///
 /// MaiaScript screen recorder library.
 /// @class MaiaRecorder
 /// @param {object}   options - Object containing options for configuring the recorder.
 ///                             It can be: startButton, pauseButton, stopButton,
 ///                                        playButton, saveButton, videoPlayer,
 ///                                        fileNamePrefix, mimeType.
 /// @return {object}  Screen recorder object.
 ///
constructor MaiaRecorder(options) {
    opts = {
        "startButton": "",
        "pauseButton": "",
        "stopButton": "",
        "playButton": "",
        "saveButton": "",
        "videoPlayer": "",
        "fileNamePrefix": "Screen-recording",
        "mimeType": "video/webm"
    }
    
    if (core.type(options) != "undefined") {
        foreach (options; key; value) {
            opts[key] = value
        }
    }

    local startButton
    local pauseButton
    local stopButton
    local playButton
    local saveButton
    local videoPlayer
    local fileNamePrefix
    local mimeType
    
    local recorder
    local recordingData = []
    local recorderStream
    local isPlaying = false
    
    startButton = document.getElementById(opts["startButton"])
    pauseButton = document.getElementById(opts["pauseButton"])
    stopButton = document.getElementById(opts["stopButton"])
    playButton = document.getElementById(opts["playButton"])
    saveButton = document.getElementById(opts["saveButton"])
    videoPlayer = document.getElementById(opts["videoPlayer"])
    fileNamePrefix = opts["fileNamePrefix"]
    mimeType = opts["mimeType"]

    ///
    /// Mixes two audio tracks and the first video track found.
    /// @method mixTracks
    /// @memberof MaiaRecorder
    /// @param {object}   trackA - Track A.
    /// @param {object}   trackB - Track B.
    /// @return {object}  Video stream containing mixed audio and video tracks.
    ///
    function mixTracks(trackA, trackB) {
        ctx := AudioContext()
        dest = ctx.createMediaStreamDestination()

        audioTracksA = trackA.getAudioTracks()
        if (audioTracksA.length > 0) {
            mediaStreamSourceA = ctx.createMediaStreamSource(trackA)
            mediaStreamSourceA.connect(dest)
        }
        audioTracksB = trackB.getAudioTracks()
        if (audioTracksB.length > 0) {
            mediaStreamSourceB = ctx.createMediaStreamSource(trackB)
            mediaStreamSourceB.connect(dest)
        }

        mixedTracks = dest.stream.getTracks()
        mixedTracks = mixedTracks.concat(trackA.getVideoTracks())
        mixedTracks = mixedTracks.concat(trackB.getVideoTracks())
        
        mediaStream := MediaStream(mixedTracks)

        return(mediaStream)
    }

    ///
    /// Return the file name for the captured video.
    /// @method getFilename
    /// @memberof MaiaRecorder
    /// @return {object}  File name for the captured video.
    ///
    function getFilename() {
        now := Date()
        timestamp = now.toISOString()
        fileName = fileNamePrefix + "-" + timestamp
        return(fileName)
    }

    ///
    /// Start recording screen.
    /// @method startRecording
    /// @memberof MaiaRecorder
    /// @return {object}  Screen recording started.
    ///
    async startRecording () {
        local userMediaStream
        local displayMediaStream
        recordingData = []

        audioOptions = {
            "video": false,
            "audio": true
        }
        videoOptions = {
            "video": {"displaySurface": "browser"},
            "audio": true
        }

        try {
            userMediaStream ?= navigator.mediaDevices.getUserMedia(audioOptions)
            displayMediaStream ?= navigator.mediaDevices.getDisplayMedia(videoOptions)
        } catch (e) {
            system.showMessageDialog("Error: screen recording is not supported by this browser.")
            return()
        }

        if (userMediaStream) {
            recorderStream = mixTracks(userMediaStream, displayMediaStream)
        } else {
            recorderStream = displayMediaStream
        }
        recorder := MediaRecorder(recorderStream, {"mimeType": mimeType})

        function onDataAvailable (e) {
            if (e.data && e.data.size > 0) {
                recordingData.push(e.data)
            }
            console.log("available")
        }
        function onStop () {
            function stopTrackRecording(track) {
                track.stop()
            }
            recorderStreamTracks = recorderStream.getTracks()
            recorderStreamTracks.forEach(stopTrackRecording)

            userMediaStreamTracks = userMediaStream.getTracks()
            userMediaStreamTracks.forEach(stopTrackRecording)

            displayMediaStreamTracks = displayMediaStream.getTracks()
            displayMediaStreamTracks.forEach(stopTrackRecording)
        }
        recorder.ondataavailable = onDataAvailable
        recorder.onstop = onStop

        function onInactive () {
            stopRecording()
            console.log(recorder.state)
        }
        recorderStream.addEventListener("inactive", onInactive)

        recorder.start()
        
        startButton.innerText = "Recording"
        startButton.disabled = true
        pauseButton.disabled = false
        stopButton.disabled = false
        playButton.disabled = true
        saveButton.disabled = true

        console.log(recorder.state)
    }
    startButton.addEventListener("click", startRecording)

    ///
    /// Stop recording screen.
    /// @method stopRecording
    /// @memberof MaiaRecorder
    /// @return {object}  Screen recording stopped.
    ///
    function stopRecording() {
        if ((recorder.state == "recording") || (recorder.state == "paused")) {
            recorder.stop()
        }

        startButton.disabled = false
        pauseButton.disabled = true
        stopButton.disabled = true
        playButton.disabled = false
        saveButton.disabled = false

        startButton.innerText = "Record"
        pauseButton.innerText = "Pause"

        console.log(recorder.state)
    }
    stopButton.addEventListener("click", stopRecording)

    ///
    /// Pause recording screen.
    /// @method pauseRecording
    /// @memberof MaiaRecorder
    /// @return {object}  Screen recording paused.
    ///
    function pauseRecording() {
        if (recorder.state == "paused") {
            recorder.resume()
            pauseButton.innerText = "Pause"
        } elseif (recorder.state == "recording") {
            recorder.pause()
            pauseButton.innerText = "Resume"
        }

        console.log(recorder.state)
    }
    pauseButton.addEventListener("click", pauseRecording)

    ///
    /// Plays the recording.
    /// @method playRecording
    /// @memberof MaiaRecorder
    /// @return {object}  The recorded video is played.
    ///
    function playRecording() {
        videoPlayer.hidden = !videoPlayer.hidden
        if ((!isPlaying) && (!videoPlayer.hidden)) {
            videoSource := Blob(recordingData, {"type": mimeType})
            videoPlayer.src = window.URL.createObjectURL(videoSource)
            videoPlayer.play()
            playButton.innerText = "Hide"
        } else {
            playButton.innerText = "Play"
        }
    }
    playButton.addEventListener("click", playRecording)
    
    function setIsPlaying() {
        isPlaying = true
    }
    function unsetIsPlaying() {
        isPlaying = false
    }
    videoPlayer.addEventListener("play", setIsPlaying)
    videoPlayer.addEventListener("pause", unsetIsPlaying)
    videoPlayer.addEventListener("playing", setIsPlaying)
    videoPlayer.addEventListener("ended", unsetIsPlaying)

    ///
    /// Saves the recording.
    /// @method saveRecording
    /// @memberof MaiaRecorder
    /// @return {object}  The recorded video is saved.
    ///
    function saveRecording() {
        blob := Blob(recordingData, {"type": mimeType})
        url = window.URL.createObjectURL(blob)
        a = document.createElement("a")
        a.style.display = "none"
        a.href = url
        a.download = getFilename() + ".webm"
        document.body.appendChild(a)
        a.click()
        function removeDownloadLink() {
            document.body.removeChild(a)
            window.URL.revokeObjectURL(url)
        }
        setTimeout(removeDownloadLink, 500)
    }
    saveButton.addEventListener("click", saveRecording)
}