Wednesday, November 11, 2026
HomeiOS Developmentjavascript - HTML5 Audio blocked on IOS

javascript – HTML5 Audio blocked on IOS

[ad_1]

I am getting the next error on IOS when i attempt to play a audio file.

Unhandled Runtime Error

NotAllowedError: The request shouldn’t be allowed by the consumer agent or the platform within the present context, presumably as a result of the consumer denied permission.

Name Stack

play

[native code]

dispatchEvent

[native code]

Right here is the code of the customized participant:

export interface IAudioPlayer {
  src: string;
  onPlay?: () => void;
}

export const AudioPlayer: FC<IAudioPlayer> = (props) => {
  const { src, onPlay } = props;
  const audioRef = useRef<HTMLAudioElement>(null);
  const [base64Src, setBase64Src] = useState<string | undefined>(undefined);
  const [{ status, playing, muted, currentTime }, dispatch] = useReducer<typeof audioReducer, null>(
    audioReducer,
    null,
    () => ({
      standing: "idle",
      enjoying: false,
      muted: false,
      currentTime: getTimeCodeFromNum(0),
    }),
  );

  const translate = useTranslations();

  const pressToPlay = translate((t) => t.media.pressToPlayLabel, "[Press to play]");
  const bufferingMessage = translate((t) => t.media.bufferingMessage, "[Buffering...]");

  const togglePlaying = async () => {
    swap (standing) {
      case "idle": {
        dispatch({ sort: "FETCHING" });
        const audio = await fetchAudio(src);
        setBase64Src(audio);
        break;
      }
      case "canPlay": {
        dispatch({ sort: "TOGGLE_PLAYING" });
        break;
      }
      default:
        break;
    }
  };

  useEffect(() => {
    const audioElement = audioRef.present;
    if (audioElement && enjoying === true) {
      const stopPlaying = () => dispatch({ sort: "TOGGLE_PLAYING", overridePlaying: false });
      audioElement.addEventListener("ended", stopPlaying);
      const interval = setInterval(
        () => dispatch({ sort: "UPDATE_CURRENT_TIME", currentTime: audioElement.currentTime }),
        500,
      );
      return () => {
        audioElement.removeEventListener("ended", stopPlaying);
        clearInterval(interval);
      };
    }
  }, [audioRef, playing]);

  useEffect(() => {
    if (standing === "canPlay") {
      if (enjoying) {
        audioRef.present?.play();
        onPlay?.();
      } else {
        audioRef.present?.pause();
      }
    }
  }, [status, playing, onPlay]);

  useEffect(() => {
    dispatch({ sort: "RESET" });
  }, [src]);

  return (
    <div className="flex flex-row justify-center items-center wager rounded-full border border-theme-blue-500 py-2 px-4">
      {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
      <audio
        src={base64Src ?? undefined}
        ref={audioRef}
        muted={muted}
        onLoadedMetadata={() => dispatch({ sort: "CAN_PLAY" })}
      />
      <button
        sort="button"
        onClick={togglePlaying}
        disabled={standing === "buffering"}
        className="transition duration-300 ease-in-out remodel hover:scale-105 h-40px w-40px bg-theme-blue-700 text-white rounded-full focus:outline-none disabled:opacity-50"
      >
        <SvgIcon className="block w-35px h-35px m-auto" iconName={enjoying ? "pause" : "play"} />
      </button>
      <div className="flex items-center justify-center px-4 min-w-0 lg:min-w-170px">
        {standing === "canPlay" && `${currentTime} / ${getTimeCodeFromNum(audioRef.present?.period)}`}
        {standing === "buffering" && bufferingMessage}
        {standing === "idle" && pressToPlay}
      </div>
      <button
        sort="button"
        onClick={() => dispatch({ sort: "TOGGLE_MUTED" })}
        className="transition duration-300 ease-in-out remodel hover:scale-105 h-30px w-30px bg-theme-blue-700 text-white rounded-full focus:outline-none"
      >
        <SvgIcon className="block w-20px h-20px m-auto" iconName={muted ? "volume-off" : "volume-up"} />
      </button>
    </div>
  );
};

interface IAudioReducerState  "buffering" 

sort IAudioReducerAction =
  | { sort: "CAN_PLAY" }
  | { sort: "TOGGLE_PLAYING"; overridePlaying?: boolean }
  | { sort: "TOGGLE_MUTED" }
  | { sort: "UPDATE_CURRENT_TIME"; currentTime?: quantity }
  | { sort: "FETCHING" }
  | { sort: "RESET" };

const audioReducer: Reducer<IAudioReducerState, IAudioReducerAction> = (prevState, motion) => {
  swap (motion.sort) {
    case "CAN_PLAY": {
      return {
        ...prevState,
        standing: "canPlay",
        enjoying: true,
      };
    }
    case "FETCHING": {
      return {
        ...prevState,
        standing: "buffering",
      };
    }
    case "TOGGLE_PLAYING":
      return {
        ...prevState,
        enjoying: motion.overridePlaying ?? !prevState.enjoying,
      };
    case "TOGGLE_MUTED": {
      return {
        ...prevState,
        muted: !prevState.muted,
      };
    }
    case "UPDATE_CURRENT_TIME": {
      return {
        ...prevState,
        currentTime: getTimeCodeFromNum(motion.currentTime),
      };
    }
    case "RESET": {
      return {
        standing: "idle",
        enjoying: false,
        muted: false,
        currentTime: getTimeCodeFromNum(0),
      };
    }
    default:
      return prevState;
  }
};

export const fetchAudio = async (src: string): Promise<string> => {
  const audioBlob = await get("/" + src);
  const reader = new FileReader();
  return new Promise(async (resolve) => {
    const blob = await audioBlob.blob();
    reader.readAsDataURL(blob);
    reader.onloadend = () => resolve(reader.end result as string);
  });
};

perform getTimeCodeFromNum(period: quantity | undefined): string {
  if (period === null || period === undefined) return "";

  let seconds = parseInt(period.toString(), 10);
  let minutes = parseInt((period / 60).toString(), 10);
  seconds -= minutes * 60;
  const hours = parseInt((minutes / 60).toString(), 10);
  minutes -= hours * 60;

  if (hours === 0) {
    return `${minutes}:${String(seconds % 60).padStart(2, "0")}`;
  }

  return `${String(hours).padStart(2, "0")}:${minutes}:${String(seconds % 60).padStart(2, "0")}`;
}

[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments