0

This code works in Chrome, but fails with Subject's syntax error in Safari in line 2. Also, in iOS the Safari user is asked for permission to use their microphone but recording fails. All is well with Android Chrome.

Fiddle shows more

Code is based on

index.js:

var recordAudio = function recordAudio() {
  return new Promise(async function(resolve) {
    var stream = await navigator.mediaDevices.getUserMedia({
      audio: true
    });
    var mediaRecorder = new MediaRecorder(stream);
    var audioChunks = [];
    mediaRecorder.addEventListener("dataavailable", function(event) {
      audioChunks.push(event.data);
    });

    var start = function start() {
      audioChunks = [];
      mediaRecorder.start();
    };

    var stop = function stop() {
      return new Promise(function(resolve) {
        mediaRecorder.addEventListener("stop", function() {
          var audioBlob = new Blob(audioChunks);
          var audioUrl = URL.createObjectURL(audioBlob);
          var audio = new Audio(audioUrl);

          var play = function play() {
            return audio.play();
          };

          resolve({
            audioChunks: audioChunks,
            audioBlob: audioBlob,
            audioUrl: audioUrl,
            play: play
          });
        });
        mediaRecorder.stop();
      });
    };

    resolve({
      start: start,
      stop: stop
    });
  });
};
zerowords
  • 2,915
  • 4
  • 29
  • 44
  • This means Safari doesn't support the `async` keyword. What version of Safari is it? It has been supported since 10.1. – Barmar May 18 '21 at 19:37
  • 1
    [Never pass an `async function` as the executor to `new Promise`](https://stackoverflow.com/q/43036229/1048572)! – Bergi May 18 '21 at 19:45
  • @Barmar it's Safari 9.1.2 . But my kids have tried it on iOS 14. But I do not have an iOS device and would not know how to get at the developer's stuff on a mobile device. Btw, the app is currently at https://contracalls.appspot.com . But only works on non-iOS Chrome as far as I can tell. Maybe you can test it on a newer Safari? – zerowords May 18 '21 at 20:45
  • Safari 9 is 5 years old. – Barmar May 18 '21 at 21:02

2 Answers2

1

Support for async functions was added in Safari 10.1. You're doing your testing in 9.1, which is too old.

If you need your application to be compatible with such an old Safari version (it's from 2016) you'll have to use explicit Promise code rather than async and await.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • On Safari 13.1.3 I get "Line 9:47 Unhandled Promise Rejection: ReferenceError: Can't find variable: MediaRecorder" Line 9 contains `var mediaRecorder = new MediaRecorder(stream);` I want it to work on mobiles only, btw – zerowords May 18 '21 at 21:30
  • 1
    You need to learn to use caniuse.com to find out what features are available in different Safari versions. MediaRecorder is available by default in 14.1, can be enabled in 12.1, and isn't available before that. – Barmar May 18 '21 at 22:47
  • Safari 9.1 is way out of date, you should forget about trying to support it. – Barmar May 18 '21 at 22:48
  • My target is iOS. caniuse (thanks) says it is available by default in 14.5, but my testers had only 14.4 . How is it made to work in the versions like 12.1 on Mac and iOS ? Can the app make it work, or does the individural user have to do something? – zerowords May 18 '21 at 23:19
  • 1
    https://www.educative.io/edpresso/how-to-enable-the-mediarecorder-api-for-safari – Barmar May 18 '21 at 23:48
  • That link almost works for the 13.1.3 (laptop). It leaves out a (required?) step to leave Safari preferences before (and restart Safari, perhaps) using the mic. But then the system preferences for sound input may need to be adjusted and monitored to see if sound is being heard. But even after all that, I was not able to hear the recordings play. But I am not totally discouraged because my real target is the iphone Safari. But I don't have an iphone so I'm going to ask my "testers" (relatives) to try it out. If anyone here gets a success I would appreciate getting feedback. – zerowords May 19 '21 at 00:31
0

You're mixing Promise and async syntax, the error from Safari is telling you that it doesn't expect an async function, which makes sense as you're resolving a promise with another promise.

Change function recordAudio to async function recordAudio and remove the new Promise entirely and just use await instead.

Any function that uses await will return a Promise anyway.

async function recordAudio() {
    var stream = await navigator.mediaDevices.getUserMedia({
      audio: true
    });
    var mediaRecorder = new MediaRecorder(stream);
    var audioChunks = [];
    mediaRecorder.addEventListener("dataavailable", function(event) {
      audioChunks.push(event.data);
    });

    var start = function start() {
      audioChunks = [];
      mediaRecorder.start();
    };

    var stop = function stop() {
      return new Promise(function(resolve) {
        mediaRecorder.addEventListener("stop", function() {
          var audioBlob = new Blob(audioChunks);
          var audioUrl = URL.createObjectURL(audioBlob);
          var audio = new Audio(audioUrl);

          var play = function play() {
            return audio.play();
          };

          resolve({
            audioChunks: audioChunks,
            audioBlob: audioBlob,
            audioUrl: audioUrl,
            play: play
          });
        });
        mediaRecorder.stop();
      });
    };

    return {
      start: start,
      stop: stop
    };
};
Keith
  • 150,284
  • 78
  • 298
  • 434
  • While it might not make sense, it doesn't seem like it would be a syntax error. – Barmar May 18 '21 at 19:38
  • Syntax-wise, it's just an ordinary function call. – Barmar May 18 '21 at 19:38
  • @Barmar `async` says _function returns a Promise_, I'd guess Safari is checking for that. `async` is just syntax sugar, so while it _can_ be a synchronous call with no `await` it may well be a syntax error in places where Safari's engine needs the function to be synchronous. – Keith May 18 '21 at 19:41
  • 1
    I just tried `y = new Promise(async function() { return 2; })` in the Safari 13.1.2 console. No error. – Barmar May 18 '21 at 19:45
  • @Barmar the function you create a promise with shouldn't be a promise. Maybe Safari's compiler can resolve that trivial case? I don't know. Safaris supports `async` and the error message points to the `function` in `async function` being the problem. – Keith May 18 '21 at 19:48
  • @Keith it's highly unlikely that Safari would find *syntax* errors like that, because they're *not* syntax errors. – Pointy May 18 '21 at 19:51
  • @Keith I get another syntax error on the first line of your code: "SyntaxError: Unexpected keyword 'function'. Expected ';' after var declaration." Btw, my index.js begins with `"use strict";` and I did not mention that. – zerowords May 18 '21 at 20:37
  • @zerowords Hmm, try replacing `var recordAudio = async function recordAudio()` with `async function recordAudio()` or `var recordAudio = async function()` as you don't need the name twice. – Keith May 18 '21 at 20:53
  • @Keith I tried your second suggestion and got the same SyntaxError as your previous correction. I did not try your first suggestion because it had no = sign or left hand to = sign. – zerowords May 18 '21 at 21:06
  • @zerowords yes, you don't need the `=` at all. It's valid JS but it appears to be `async function` as an assignment rather than a declaration that Safari doesn't like. Try either the declaration syntax (no `=`) or see if lambda syntax works: `var recordAudio = async () => {...` – Keith May 18 '21 at 21:13