This is the documentation for MyVoiceCloud.js library.

Beside your dist of MyVoiceCloud.js, we’re using a couple of great external libraries for our services to look and work the way you see on our demo site:

jQuery, jQuery Marquee, Popper, Font-Awesome

See link references below in code example.

General Options:

Name Required? Type Description
targetSelectorId No str Id (#item) of element if you want to use out-of-box recording frontend
clientId Yes int ClientID assigned from MyVoiceCloud on subscription
apiUrl Yes string Url to API server: https://api.myvoicecloud.com
hideFrontend No true/false Used to hide/show library built-in frontend recorder
clientEntity Yes string Dynamic Internal User ID from you platform to connect your member to create recordings. E.g. fill variable with your members WordPress id using get_current_user_id()

Recording Events:

Name Description    
record-started This event is called when recording is started    
record-stopped This event is called when recording is stopped    
file_recorded This event is called when server sends response after client recording has been processed    
export-started This event is called when server sends response after client recording started to export into targeted file format (e.g. from wav to m4a or mp3    
rendered This event is called when internal recorder is rendered    
microphone-not-allowed This event is called when the user denies access to the microphone    

Methods:

Method name Description
recorder.on(eventName,callback) or MyVoiceCloud.on(eventName,callback) Register event listener by name eventName with callback. See “Events” section for details.
MyVoiceCloud.init

Initialize MyVoiceCloud instance with options from init.

IMPORTANT! Only one MyVoiceCloud instance can be initialised per page.

MyVoiceCloud.onRecording(callback(data)) Register event listener for detecting states of recording: sound levels & chunks
MyVoiceCloud.stopRecording({
title: ‘TITLE OF PRODUCT’,
tags: ‘product_sometagname’
})
Stops current recording, sets the title and product to save as.
‘TITLE OF PRODUCT’ = Save whatever title you or your user has set.
‘product_sometagname’ = Match your exact tags from the product setup in MyVoiceCloud Admin field Category Tag. This variable must be set starting with static text product_
MyVoiceCloud.startRecording() Starts recording process
MyVoiceCloud.pauseRecording() Pause current recording. Maximum pause limit is 250s. After that time client recording will be removed from caches on server.
recorder.clientAPI.getProduct(‘product_sometagname’)

Get product information by tag. Returns variables from the settings on the product and available backtracks:

id, created_at, updated_at, title, background (image url), transcription, speed, options, audio, visibility, client_id, export_format, backtrack (mp3 format), backtrack_ogg (ogg format)

See examples for details.

GET /api/v0/product/info/<clientId> Returning all products for the clientId

Responses:

This is what’s brought back to you when using the methods above:

recorder.on(eventName,callback) or MyVoiceCloud.on(eventName,callback)

</p>
rendered

"It's working!"
<p>

MyVoiceCloud.init

</p>
targetSelectorId: '#audio-player'
clientId: 1584043576
apiUrl: "https://api.myvoicecloud.com"
clientEntity: 21
hideFrontend: true
hideRecorder: undefined
<p>

MyVoiceCloud.onRecording(callback(data))

</p>
(Shown continuously while recording e.g.)
record in progress
record event
Recording is in the making:
recordingTime: 0.09
buffer: [Float32Array(4096)]
levels: [0.00157925240127278]
<p>

MyVoiceCloud.stopRecording({ title: ‘TITLE OF PRODUCT’, tags: ‘product_sometagname’ })

</p>
event is called: export-started
<p>

MyVoiceCloud.startRecording()

</p>
 
<p>

MyVoiceCloud.pauseRecording()

</p>
 
<p>

MyVoiceCloud.on(‘file_recorded’)

</p>
event is called: file_recorded
<p>

recorder.clientAPI.getProduct(‘product_sometagname’)

</p>
(Example JSON REST API Response Demo product)
 id: 21
 created_at: "2020-03-12 20:24:06"
 updated_at: "2020-03-14 15:18:13"
 title: "Serenity Prayer"
 background: "products/March2020/uBjujkZuSDlpdkIX9kpd.jpg"
 transcription: "God, give me grace to accept with serenity…"
 speed: 15
 options: "1"
 category_tag: "serenity"
 audio: "products/21/an-old-fashioned-silent-night_by_jon-gegelman_Artlist.wav"
 visibility: "1"
 client_id: 1584043576
 export_format: "mp3"
 backtrack: "https://api.myvoicecloud.com/storage//products/19.03.2020/1584578666168.mp3"
 backtrack_ogg: "https://api.myvoicecloud.com/storage//products/19.03.2020/1584578666168.ogg"

<p>

GET /api/v0/product/info/<clientId>

</p>
(Example JSON REST API Response Demo product)
"id": 21,
        "created_at": "2020-03-12 20:24:06",
        "updated_at": "2020-03-14 15:18:13",
        "title": "Serenity Prayer",
        "background": "products/March2020/uBjujkZuSDlpdkIX9kpd.jpg",
        "transcription": "<p>God, give me grace to accept with <strong>serenity</strong></p>\r\n<p>the things that cannot be changed,</p>...",
        "speed": 15,
        "options": "1",
        "category_tag": "serenity",
        "audio": "products/21/an-old-fashioned-silent-night_by_jon-gegelman_Artlist.wav",
        "visibility":"1",
        "client_id": 1584043576,
        "export_format": "mp3"
<figure class="wp-block-image size-large"></figure>
<p>

Use GET https://api.myvoicecloud.com/api/v0/product/info/<clientId> for dynamic listing of products.

To manually assign and create new recordings: Send your product tags as POST or GET values into the recorder page.

 

 

List Products Page
// External libs and your js lib for MyVoiceCloud
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery.marquee@1.5.0/jquery.marquee.min.js" type="text/javascript"></script>
<script src="https://<yourdomain>.myvoicecloud.com/dist/myVoiceCloud.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

    <script>
        $(document).ready(function () {
          $('.btn-save').hide(); //We're hiding the save button to show it once recording has been stopped
          $('.btn-pause').attr('disabled','disabled'); //And disabling the paus and stop button to enable them once recording has started
          $('.btn-stop').attr('disabled','disabled');

            MyVoiceCloud.on('rendered', function (e) {
                console.log('MyVoiceCloud has rendered and events are working!', e);
            });

            var recorder = MyVoiceCloud.init({
                targetSelectorId: '#audio-player', //you can put your own id here for your player when necessary

                clientId: <your unique id with us>, //Your MyVoiceCloud clientId
                apiUrl: 'https://api.myvoicecloud.com',
                hideFrontend: true, //set to false to show the player, set to true to hide the player
                clientEntity: <your dynamic members id>, // This is the User identifier
            });
            var mq = $('.marquee').marquee(); // Loading the marquee element into a variable
            recorder.clientAPI.socket.on('connected', function (e) {
                console.log('Socket connected', e); // The socket for recording has been connected

                recorder.clientAPI.socket.on('file_recorded', function (e) {
                    console.log('The recorded file has been processed and is ready to be played', e);
                });
            });

            MyVoiceCloud.onRecording(function (e) {
                console.log('Recording is in the making: ', e);

                var minutes = Math.floor(e.recordingTime / 60);
                var seconds = "0" + (parseInt(e.recordingTime - minutes * 60));
                seconds = seconds.substr(-2);

                $('.timing').html("Elapsed time: " + minutes + ":" + seconds); // Showing and updating the duration time of recording

                $('.levels').html(e.levels[0]); // Showing and updating the volume bar

                $('.ar-sound-now').css('background-color', '#52a727bd');
                if (e.levels[0] >= 17) {
                    $('.ar-sound-now').css('background-color', '#ffe500bd');
                }
                if (e.levels[0] >= 24) {
                    $('.ar-sound-now').css('background-color', '#ed0909d0');
                }
                $('.ar-sound-now').css('width', (e.levels[0] * 3) + '%'); // Showing and updating the volume level

                $('.ar-sound-now').attr('aria-valuenow', e.levels[0] * 3);
            });

            //Lets fetch info about the product using getProduct call
            recorder.clientAPI.getProduct('{{$product->category_tag}}',function(response){
                console.log('Response from getProduct, this is your smorgasbord of stuff to choose from: ', response);
                $('.marquee').marquee('destroy'); //Resetting existing marquee element
                //Load new content using Ajax and update the marquee element
               mq = $('.marquee').html(response.data.transcription)
              //Apply stuff to marquee again
              .marquee({ speed: response.data.speed, }); // Setting scroll speed from product settings in MyVoiceCloud admin
              mq.marquee('pause'); // Make sure marquee is ready to scroll

              recording_name = $('.recording_name').text(response.data.title); // Lets put the original title into the span element

              bgImage = $('.bgImage').attr('src', '/storage/'+response.data.background); // Set product image element src from getProduct response

              //Test if browser supports ogg or if we have to use mp3
              function supportsAudioType(type) {
                 let audio;

                 let formats = {
                 ogg: 'audio/ogg',
                 mp3: 'audio/mpeg'
                 };

                 if(!audio) {
                 audio = document.createElement('audio')
                 }

                 return audio.canPlayType(formats[type] || type);
             }

             if(supportsAudioType('ogg') != "") {
               // Set backtrack element src and type to support ogg
                backtrack = $('#backtrack').attr('src', response.data.backtrack_ogg);
                backtrack_type = $('#backtrack').attr('type', 'audio/ogg');
             }
             else {
               // Set backtrack element src and type to support mp3
                backtrack_mp3 = $('#backtrack').attr('src', response.data.backtrack);
                backtrack_type = $('#backtrack').attr('type', 'audio/mpeg');
             }
            }); // getProduct ends here

            // 1. Listen for changes of the contenteditable element
            var content = document.querySelector('[contenteditable]');
            content.addEventListener('input', function(event) {
              // 2. Retrieve the text from inside the element and update recording_name so we have the latest edit on save
              recording_name = content.innerHTML;
            });

            // Lets make sure we ignore the enter key as it adds a nbsp to the input
            $('[contenteditable]').on('keypress', function(e) {
                if(!/[A-Za-z0-9\s]/.test(e.key)) {
                  e.preventDefault();
                }
                if (e.which == 13) {
                  $('[contenteditable]').trigger('blur'); // Blur focus from the input field, looks neat
                };
                 //ignores enter key
                 return e.which != 13;
            });

            //Variables for pause timeout function
            var saveTimeout = null;
            var pauseInterval = null;

            $('.btn-start').click(function () {
                //Let's enable the paus and stop button as recording has started
                $('.btn-pause').removeAttr('disabled');
                $('.btn-stop').removeAttr('disabled');
                MyVoiceCloud.startRecording(); // Starts recording process

                if(pauseInterval){
                    clearInterval(pauseInterval);
                    pauseInterval = null;
                    $('.btn-pause').text($('.btn-pause').data('caption'));
                }else {
                    clearTimeout(saveTimeout);
                }
            });

            $('.btn-pause').click(function(){
                MyVoiceCloud.pauseRecording(); // Pause recording on pause button click
                var caption = $('.btn-pause').data('caption');
                if(!pauseInterval) {
                    $('#transcript').attr('scrollamount',0); // Stop the scroll of the marquee element
                    var secondsCount = 250; // Set timer to 5 minutes
                    pauseInterval = setInterval(function () {
                        if (secondsCount < 0) {

                            clearInterval(pauseInterval);
                            pauseInterval = null;

                        }else{
                            // Countdown 5 minutes in the button caption
                            $('.btn-pause').text(caption + '('+secondsCount+'s.)');
                            secondsCount--;
                            }
                    }, 1000);
                  }
            });

            $('.btn-stop').click(function(){
                // Disable the buttons while processing the recorded file
        			  $('.btn-start').attr('disabled','disabled');
        				$('.btn-pause').attr('disabled','disabled');
        				$('.btn-stop').attr('disabled','disabled');
        				$('.btn-save').attr('disabled','disabled');
        				$('.btn-save').show(); // Show save button
                    MyVoiceCloud.stopRecording({title: $('.recording_name').html(), tags: 'product_{{$product->category_tag}}'}); // Stops current recording, sets the title and product to save as
    	      });

            MyVoiceCloud.on('record-started',function(){ // Recording has started
                        $("#backtrack").get(0).play();; // Start the backtrack audio
        				mq.marquee('resume'); // Start scrolling the text
            });

            MyVoiceCloud.on('file_recorded', function (data) { // This event is called when server sends response after client recording has been processed. Recording has been transfered, file is ready to play, update caption, enable save button to take user back to list of recordings
                $('.btn-save').text('GO TO LIST');
                $('.btn-save').removeAttr('disabled');
            });

            MyVoiceCloud.on('record-stopped',function(){
                mq.marquee('pause'); // Pause text from scrolling
                document.getElementById('backtrack').pause(); // Pause backtrack audio from playing
            });

            MyVoiceCloud.on('export-is-started',function(){
                // This event is called when server sends response after client recording started to export into targeted file format (e.g. from wav to m4a or mp3)
                $('.btn-save').text('Almost done now!');
            });
        });
    </script>
    <div class="container">
        <div class="row">
            <div class="col-sm-9">
                <div class="row">
                    <div class="col-md-1" style="display: flex; align-items: center;">
                      <image class="bgImage" src="" style="width: 75px; border-radius: 50px;">
                    </div>
                    <div class="col-md-11" style="display: flex; align-items: center;">
                        <h1 class="content-heading" style="text-transform: uppercase; margin-bottom: 0px !important; margin-top: 0px !important; position: relative; left: 10px !important;">{{$product->title}}</h1>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-12">
                      <p>Your new track will be saved as: "<span contenteditable="true" class="recording_name"></span>"<i class="fa fa-pencil" aria-hidden="false"> (Click at the name the set your own)</i></p>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-12">
                        <p class="">Lets record your own voice for the track {{$product->title}}! This whole new audio experience will be created once you've recorded and saved your session.</p>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-8 col-sm-12">
                        <div class="marquee"  data-duration='5000'  data-direction="up" >

                        </div>

                        <div>Levels: <span class="levels"></span></div>
                        <div class="ar-sound-level">
                            <div class="ar-sound-now"></div>
                        </div>
                        <div>Timing: <span class="timing"></span></div>
                    </div>
                    <div class="col-md-4 col-sm-12">
                        <p>INSTRUCTIONS<br/>
                            Make sure you don't distort your recording by talking to closely or to loudly into your microphone.
                            <br/>
                            Hit the record button and start read the
                            transcript as it scrolls up.
                        </p>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-3">
                        <audio
                          class="backtrack"
                        id="backtrack"
                        src=""
                        type=""
                        preload="auto"
                        >
                        </audio>
                        <button type="button" class="btn btn-danger btn-lg btn-block btn-start" onclick="document.getElementById('backtrack').load();">RECORD</button>
                    </div>
                    <div class="col-md-3">
                        <button type="button" id="btn-pause" class="btn btn-secondary btn-lg btn-block btn-pause" data-caption="PAUSE" onclick="">PAUSE
                        </button>
                    </div>
                    <div class="col-md-3">
                        <button type="button" class="btn btn-secondary btn-lg btn-block btn-stop" onclick="">STOP
                        </button>
                    </div>
                    <div class="col-md-3">
                        <button type="button" class="btn btn-dark btn-lg btn-block btn-save" onclick="location.href='{{route('records')}}'">
                            <span class="spinner-border" style="padding: 0px !important; margin-right: 10px; width: 2rem; height: 2rem;" role="status" aria-hidden="true"></span>Wait While Saving...
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <style>
      .marquee {
      	overflow: hidden;
        height:300px;
    	}
      @media only screen and (max-width: 480px) {
        .marquee {
        height: 150px;
    }
}

      [type=button]:not(:disabled), [type=reset]:not(:disabled), [type=submit]:not(:disabled), button:not(:disabled) {
        cursor: pointer;
      }
      [type=button], [type=reset], [type=submit], button {
        -webkit-appearance: button;
      }
    </style>

 

 

Recording Page
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>

    <script src="https://<yourdomain>.myvoicecloud.com/dist/myVoiceCloud.min.js"></script>

    <script>
        $(document).ready(function () {
            $('#myModal').appendTo("body"); // This is a delete modal

            MyVoiceCloud.on('rendered',function (e) {
                console.log('events are working!', e);
            });

            var recorder = MyVoiceCloud.init({
                targetSelectorId: '#audio-player', //you can put your own id here for your player when nessecary
                clientId: <your unique id with us>,
                apiUrl: 'https://api.myvoicecloud.com',
                hideFrontend: false,
                clientEntity: <your dynamic members id>,
                hideRecorder: true,
            });

            console.log(recorder.clientAPI);

            recorder.clientAPI.getPlayList(function(response){
                response.data = response.data.reverse();
                for(var i in response.data){
                     var item = response.data[i];
                     var newItm = $('#player_donor').clone(true);
                     newItm.find('.title').html(item.name_of_file);
                     newItm.find('.tags').html(item.tags.replace('_',' '));
                     newItm.find('audio source').attr('src','https://api.myvoicecloud.com/storage/'+item.path);
					          newItm.find('.created_at').html('Created '+item.created_at);
                     newItm.find('.remove').data('id',item.id);
          					newItm.addClass('recording_'+item.id);
          					if(i>0){
          						newItm.removeClass('recording_'+response.data[0].id);
          					};
          					newItm.find('.remove').click(function(){
          						$('.delete-modal').data('id',$(this).data('id'));
          					});
                     $('.records').append(newItm);
                };
            });
            $('.delete-modal').click(function(e){
      				var dataID = $(this).data('id');
      				recorder.clientAPI.deleteFile($(this).data('id'),function(ret) {
      					$('.recording_'+dataID).remove();
                  },function (e) {
                  	console.log(e);
              });
            });
        });
    </script>
    <h2 class="content-heading">Recordings</h2>
    <div class="content-description">List of My Recordings</div>
    <hr/>
    <div>
        <div style="display: none;">
            <div class="" id="player_donor" data-id="" style="padding: 20px;">
                <div>
                    <h3 class="title"></h3>
                  </div>
                <div>
                  <h5>
                    <span hidden class="tags"></span>
                  </h5>
                  <span class="created_at"></span>
                </div>
                <div>
                  <audio controls>
                    <source src="" type="audio/wav">
                        Your browser does not support the audio element.
                  </audio>
                </div>
                <div style="float: right;">
                  <button class="remove" data-toggle="modal" data-target="#myModal" style="padding: 1px 7px 7px;">
                    <svg class="tcb-icon" viewBox="0 0 448 512" data-id="icon-trash-alt-light" data-name="">
                      <path d="M296 432h16a8 8 0 0 0 8-8V152a8 8 0 0 0-8-8h-16a8 8 0 0 0-8 8v272a8 8 0 0 0 8 8zm-160 0h16a8 8 0 0 0 8-8V152a8 8 0 0 0-8-8h-16a8 8 0 0 0-8 8v272a8 8 0 0 0 8 8zM440 64H336l-33.6-44.8A48 48 0 0 0 264 0h-80a48 48 0 0 0-38.4 19.2L112 64H8a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h24v368a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V96h24a8 8 0 0 0 8-8V72a8 8 0 0 0-8-8zM171.2 38.4A16.1 16.1 0 0 1 184 32h80a16.1 16.1 0 0 1 12.8 6.4L296 64H152zM384 464a16 16 0 0 1-16 16H80a16 16 0 0 1-16-16V96h320zm-168-32h16a8 8 0 0 0 8-8V152a8 8 0 0 0-8-8h-16a8 8 0 0 0-8 8v272a8 8 0 0 0 8 8z"></path>
                    </svg>
                  </button>
                </div>
            </div>
        </div>
        <div class="row records">

        </div>
    </div>
    <div id="myModal" class="modal fade">
    	<div class="modal-dialog modal-confirm">
    		<div class="modal-content">
    			<div class="modal-header">
    				<h4 class="modal-title">Are you sure?</h4>
    			</div>
    			<div class="modal-body">
    				<p>Do you really want to delete this record?</p>
            <p>This process cannot be undone.</p>
    			</div>
    			<div class="modal-footer">
    				<button type="button" class="btn btn-info btn-sm" data-dismiss="modal">CANCEL</button>
    				<button type="button" class="btn btn-danger delete-modal btn-sm btn-save" data-id="" data-dismiss="modal">DELETE</button>
    			</div>
    		</div>
    	</div>
    </div>
    <style type="text/css">
    	.modal-confirm {
    		color: #636363;
    	}
    	.modal-confirm .modal-content {
    		padding: 5px;
    		border-radius: 5px;
    		border: none;
        align-items: center;
        text-align: center;
    		font-size: 14px;
    	}
      .modal-content {
        width: auto !important;
      }
    	.modal-confirm .modal-header {
    		border-bottom: none;
        position: relative;
    	}
    	.modal-confirm h4 {
    		text-align: center;
    		font-size: 26px;
    		margin: 5px 0 -10px -10px;
    	}
    	.modal-confirm .close {
        position: absolute;
    		top: -5px;
    		right: -2px;
    	}
    	.modal-confirm .modal-body {
    		color: #333333;
    	}
      .modal-confirm .modal-footer {
    		border: none;
    		text-align: center;
    		border-radius: 5px;
    		font-size: 13px;
    		padding: 10px 15px 25px;
    	}
    	.modal-confirm .modal-footer a {
    		color: #999;
    	}
    	.modal-confirm .icon-box {
    		width: 80px;
    		height: 80px;
    		margin: 0 auto;
    		border-radius: 50%;
    		z-index: 9;
    		text-align: center;
    		border: 3px solid #f15e5e;
    	}
    	.modal-confirm .icon-box i {
    		color: #f15e5e;
    		font-size: 46px;
    		display: inline-block;
    		margin-top: 13px;
    	}
      .modal-confirm .btn {
        color: #fff;
        border-radius: 4px;
        background: #60c7c1;
        text-decoration: none;
        transition: all 0.4s;
        line-height: normal;
        min-width: 80px;
        border: none;
        min-height: 40px;
        border-radius: 3px;
        margin: 0 5px;
        outline: none !important;
      }
    	.modal-confirm .btn-info {
            background: #c1c1c1;
        }
        .modal-confirm .btn-info:hover, .modal-confirm .btn-info:focus {
            background: #a8a8a8;
        }
        .modal-confirm .btn-danger {
            background: #f15e5e;
        }
        .modal-confirm .btn-danger:hover, .modal-confirm .btn-danger:focus {
            background: #ee3535;
        }
        .tcb-icon {
            display: inline-block;
            width: 1.5em;
            height: 1.5em;
            line-height: 1em;
            vertical-align: middle;
            stroke-width: 0;
            stroke: currentColor;
            fill: currentColor;
            -webkit-box-sizing: content-box;
            box-sizing: content-box;
        }
        svg {
            overflow: hidden;
            vertical-align: middle;
        }
        [type=button]:not(:disabled), [type=reset]:not(:disabled), [type=submit]:not(:disabled), button:not(:disabled) {
          cursor: pointer;
        }
        [type=button], [type=reset], [type=submit], button {
          -webkit-appearance: button;
        }
    </style>

List Recordings Page

Updates:

</p>
<pre class="wp-block-syntaxhighlighter-code">Rev 1.9 2021-12-08 Added status for processing to playlist array. Fixed bug when user access playlist page right after recording, the MP3 processing takes some time to present the final track. By checking the processing->status you can choose to show additional message if status is not "done"<br>Rev 1.8 2021-10-14 Dynamic sample rate fixed, voice was not mixed correctly when recorded with >48khz sample rate on client side
Rev 1.7 2021-05-26 Fixed bug for list not showing while processing new recording
Rev 1.6 2021-02-21 Fixed bug for pause button, music started from beginning when continuing recording after pause. Updated onRecording function with converting time to minutes and seconds, added change color to volume depending on gain input
Rev 1.5 2020-12-21 Updated event for export-started 
Rev 1.4 2020-03-23 New endpoint for listing products 
Rev 1.3 2020-03-22 Added updated code for backtracks, delete popper, script includes, custom title and recording flow. Sectioned usage code per page.
Rev 1.2 2019-12-20 Updated names of record events, added description for getProduct 
Rev 1.1 2019-09-19 Added description for MyVoiceCloud.on event, added event microphone-not-allowed

Rev 1.0 2019-09-06 Document created</pre>
<p>