μλ‘μ΄ νλ‘μ νΈλ₯Ό λ§λλλ°, μ€λμ€λ₯Ό μλμ¬μνλ κΈ°λ₯μ΄ λ€μ΄κ°λ€.
μ¦, μμκ±°κ° μλ μνμμ μ€λμ€κ° μλμ¬μλμ΄μΌ νλ€.
λ°μ€ν¬ν ν¬λ‘¬κ³Ό λͺ¨λ°μΌ ν¬λ‘¬, λ°μ€ν¬ν μ¬ν리μμλ μ λμνλλ°, λͺ¨λ°μΌ μ¬ν리μμ ν΄λΉ κΈ°λ₯μ΄ λμνμ§ μμλ€.
λͺ¨λ°μΌ μ¬ν리μμ λμ΄ μλ¬λ λ€μκ³Ό κ°λ€.
Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
μ΄λ² νλ‘μ νΈμμ κ°μ₯ μ€μν κΈ°λ₯μ΄λΌμ(π±) μ΄λ»κ²λ λμκ°κ² ν΄μΌ ν΄μ κΌΌμ λΆλ¦΄μ°νν λ°©λ²μ λν΄ κ³ λ―Όν΄λ³΄μλ€.
π‘ tl;dr
1. λΈλΌμ°μ λ§λ€ λ―Έλμ΄ μλμ¬μ μ μ± μ΄ λ€λ₯΄λ―λ‘ μ μ± μ μ μμ보μ.
2. μ¬μ©μ μ μ€μ²μ μ§μ μ μΈ κ²°κ³Όλ‘ λ―Έλμ΄ μλμ¬μνλ κ²μ κ°λ₯νλ€.
3. ν΄λ¦ νΉμ submit μ΄λ²€νΈκ° λ°μνμλ§μ μμ μ€λμ€λ₯Ό μμκ±°λ‘ μ¬μνκ³ , μ€μ μ¬μλμ΄μΌ ν srcλ₯Ό λ°μΌλ©΄ μ€λμ€μ srcλ₯Ό κ΅μ²΄ λ° μμκ±° ν΄μ ν ν μ¬μνμ¬ ν΄κ²°νλ€.
μ΄λ² νλ‘μ νΈλ μ¬μ©μκ° νΉμ λ²νΌμ ν΄λ¦νκ±°λ μ
λ ₯κ°μ Submitνλ©΄ APIμ ν΅μ νκ³ μλ΅μ κ²°κ³Όλ₯Ό 보μ¬μ€ ν μ€λμ€κ° μλμ¬μλλ νλ‘μ°μ΄λ€.
μ¦, μ¬μ©μκ° μ§μ μ€λμ€λ₯Ό μ¬μνλ μν©μ΄ μλλ©° APIμ κ²°κ³Όκ° λ λλ§λ ν μλμ¬μλλλ‘ μ½λλ₯Ό μμ±νμλ€.
λ―Έλμ΄ μλμ¬μμ λν λΈλΌμ°μ μ μ± μ μ°Έκ³ ν΄μ μ°νν λ°©λ²μ΄ μλμ§ κ³ λ―Όν΄λ³΄μ.
λ¨Όμ Β MDNμΌλ‘ autoplay νμ© μ‘°κ±΄μ μ°Ύμλ΄€μ΅λλ€. λ¬Έμμ λ°λ₯΄λ©΄ μλ 쑰건 μ€ μ μ΄λ νλλ₯Ό μΆ©μ‘±ν΄μΌ autoplayλ₯Ό μ¬μ©ν μ μμ΅λλ€.
- μ€λμ€κ° μμκ±° μνκ±°λ λ³Όλ₯¨μ΄ 0μΌλ‘ μ€μ λ κ²
- μ¬μ©μκ° μ¬μ΄νΈμ μνΈμμ© (ν΄λ¦, ν, ν€ λλ₯΄κΈ° λ±) ν κ²
- μ¬μ΄νΈκ° autoplay νμ© μ¬μ΄νΈμΌ κ² (λΈλΌμ°μ κ° μλμΌλ‘ λ±λ‘νκ±°λ μ¬μ©μκ° μλμΌλ‘ μ¬μ΄νΈμ autoplayλ₯Ό νμ©νλ κ²½μ°)
- Permissions Policyλ₯Ό μ¬μ©ν΄
<iframe>
κ³Ό λνλ¨ΌνΈμ autoplay κΆνμ λΆμ¬νλ κ²½μ°
MDN λ¬Έμλλ‘λΌλ©΄ ν΄λ¦κ³Ό submitμ²λΌ μ¬μ©μμ μ¬μ΄νΈμ μνΈμμ©μ΄ μκΈ° λλ¬Έμ μλμ¬μμ΄ λμ΄μΌ νλ€.
κ·Έλμ ν¬λ‘¬μμλ λ¬Έμ κ° μμλ κ²μΌλ‘ 보μΈλ€.
λΈλΌμ°μ λ§λ€ λ―Έλμ΄ μλμ¬μ μ μ±
μ΄ λ€λ₯Έ κ²μΌλ‘ μΆμ λμ΄ μ‘°κΈ λ μ°Ύμ보μλ€.
A note about the user gesture requirement: when we say that an action must have happened βas a result of a user gestureβ, we mean that the JavaScript which resulted in the call to
video.play()
, for example, must have directly resulted from a handler for a touchend, click, doubleclick, or keydown event. So,button.addEventListener(βclickβ, () => { video.play(); })
would satisfy the user gesture requirement.video.addEventListener(βcanplaythroughβ, () => { video.play(); })
would not.
μ¬μ©μ μ μ€μ² μ건μ λν μ°Έκ³ μ¬ν: "μ¬μ©μ μ μ€μ²μ κ²°κ³Ό"λ‘ λμμ΄ λ°μν΄μΌ νλ€λ κ²μ, μλ₯Ό λ€μ΄video.play()
λ₯Ό νΈμΆν μλ°μ€ν¬λ¦½νΈκ° touchend, click, doubleclick λλ keydown μ΄λ²€νΈμ νΈλ€λ¬μμ μ§μ λ°μνμ΄μΌ νλ€λ μλ―Έμ λλ€. λ°λΌμbutton.addEventListener('click', () => { video.play(); })
λ μ¬μ©μ μ μ€μ² μꡬ μ¬νμ μΆ©μ‘±νμ§λ§,video.addEventListener('canplaythrough', () => { video.play(); })
λ μΆ©μ‘±νμ§ λͺ»ν©λλ€.
μΆμ²: New <video> Policies for iOS
Webkit λ¬Έμμμ directly resulted
λΌκ³ λͺ
μλμ΄ μλ€.
μ¦, μ¬μ©μ μ μ€μ²μ μ§μ μ μΈ κ²°κ³Όλ‘ μ¬μκ³Ό κ°μ JavaScript νΈμΆμ νμ©νλ€λ λ»μ΄λ€.
λ΄ μκ°μ νμ¬ clickκ³Ό submitμ μ§μ μ μΈ κ²°κ³Όλ‘ λ λλ§νκ³ μμ§, μ€λμ€λ₯Ό μ¬μνλ κ²μ΄ μλλ―λ‘ λΈλΌμ°μ (λͺ¨λ°μΌ μ¬ν리)μμ μλ¬λ₯Ό μΆλ ₯ν κ² μλκ° μΆμλ€.
μ¬κΈ°μ μμ΄λμ΄λ₯Ό μ°©μνλ€!
clickκ³Ό submitμ΄ λ°μνμλ§μ μ무 μ€λμ€λ₯Ό μμκ±°λ‘ μ¬μνλ€κ° μ¬μνλ €λ μ€λμ€ μμ€λ₯Ό λ°μΌλ©΄ κ·Έλ μ€λμ€λ₯Ό μ¬μνλ©΄ λμ§ μμκΉ?
κ²°κ³Όμ μΌλ‘λ§ λ³΄λ©΄ μ¬μ©μμ μ§μ μ μΈ μνΈμμ©μΌλ‘ μ€λμ€κ° μ¬μλκ³ , μ¬μ§μ΄ μμκ±°λ‘ μ¬μλκ³ μμμΌλκΉ λΈλΌμ°μ μμ μ λ§μ κ±° κ°μλ°??
κΈ°μ‘΄μλ μ€λμ€κ° μλμ¬μλμ΄μΌ ν λ λ°λ‘ μ¬μνλλ‘ μμ±νλ€.
μμ μ½λμ΄λ―λ‘ μ΄λ° λλμΌλ‘ μμ±νꡬλ μ λλ‘λ§ μ°Έκ³ νλ©΄ μ’κ² λ€.
const audioRef = useRef<HTMLAudioElement>(null);
const [audioSrc, setAudioSrc] = useState<string | null>(null);
const fetchAudioSrc = useCallback(() => {
// 3μ΄ ν μ€λμ€ μμ€ κ°μ Έμ€κΈ°
setTiemout(() => {
const src = "new audio source";
setAudioSrc(src);
}, 3000);
}, []);
useEffect(() => {
if (audioRef.current && audioSrc) {
audioRef.current.src = audioSrc;
audioRef.current.onplay = () => { ... };
audioRef.current.onended = () => { ... };
audioRef.current.play();
}
}, [audioSrc]);
return (
...
<audio hidden ref={audioRef} />
<button onClick={fetchAudioSrc}>Fetch Audio Source</button>
...
);
μ€λμ€ μ¬μ(audioRef.current.play()
)μ΄ μ¬μ©μμ μ§μ μ μΈ ν΄λ¦μΌλ‘ λ°μν κ² μλκ³ μν(audioSrc
) λ³κ²½μΌλ‘ μΈν΄ λ°μλ κ²μ΄κΈ° λλ¬Έμ λͺ¨λ°μΌ μ¬ν리μμ μλ¬λ₯Ό λ΄λ κ²μΌλ‘ μμ¬λμλ€.
buttonμ click μ΄λ²€νΈμ μ€λμ€λ₯Ό λ°λ‘ μ¬μνλλ‘ μμ νμλ€.
const audioRef = useRef<HTMLAudioElement>(null);
const [audioSrc, setAudioSrc] = useState<string | null>(null);
// μΆκ°
const playFakeAudio = useCallback(() => {
if (audioRef.current) {
audioRef.current.src = "μ무 μ€λμ€ μμ€";
audioRef.current.muted = true;
audioRef.current.onplay = null;
audioRef.current.onended = null;
}
}, []);
const fetchAudioSrc = useCallback(() => {
playFakeAudio(); // μΆκ°
setTiemout(() => {
const src = "new audio source";
setAudioSrc(src);
}, 3000);
}, []);
useEffect(() => {
if (audioRef.current && audioSrc) {
audioRef.current.src = audioSrc;
audioRef.current.muted = false; // μΆκ°
audioRef.current.onplay = () => { ... };
audioRef.current.onended = () => { ... };
}
}, [audioSrc]);
return (
...
<audio autoPlay hidden ref={audioRef} /> {/* autoPlay μΆκ° */}
<button onClick={fetchAudioSrc}>Fetch Audio Source</button>
...
);
audio
νκ·Έμ autoPlay
μμ±μ μΆκ°νμ¬ λ―Έλμ΄κ° μ¬μ κ°λ₯ν λ μλμΌλ‘ μ¬μλκ² νμλ€.
κ·Έλ¦¬κ³ λ²νΌμ ν΄λ¦νλ©΄ playFakeAudio()
λ₯Ό νΈμΆνμ¬ 3μ΄ μ κΉμ§ audio
κ° μμκ±° μνμμ μ¬μλκ²λ νμλ€.(audioRef.current.muted = true;
)
3μ΄ νμλ μ€μ μ¬μλμ΄μΌ νλ μμ€λ₯Ό κ°μ Έμ€κΈ° λλ¬Έμ useEffect
μμ audio
μ src
λ₯Ό λ°κΎΈκ³ muted
λ₯Ό ν΄μ νμ¬, ν΄λ¦ μ΄λ²€νΈ λλ¬Έμ μ΄λ―Έ μ¬μ μ€μ΄λ μ€λμ€κ° μ리μ ν¨κ» μ¬μνκ² μ²λ¦¬νμλ€.
κ³νλλ‘ (ν
μ€νΈ κ°λ₯ν..) λͺ¨λ λΈλΌμ°μ μμ μ€λμ€κ° μλμ¬μλμλ€.
λͺ¨λ μ¬ν리 λͺ¨λ°μΌ λ²μ μμ ν
μ€νΈν΄λ³΄μ§λ λͺ»νμΌλ, iOS 15~17μμλ λ¬Έμ μμλ€.
κΈ°λ₯ ꡬνμ μν΄ λΆνμν 리μμ€λ₯Ό λ€μ΄λ°μμΌ νλ λ¨μ μ΄ μμ§λ§, ν¬κΈ°κ° μμ μμ€λ₯Ό μ¬μ©νλ€λ©΄ μ¬μ©μμκ² λΆλ΄μ λ μ€ κ²μΌλ‘ μμνλ€.
λ―Έλμ΄ μλμ¬μμ ꡬνν λ λΈλΌμ°μ μ μ±
μ μ½μ΄λ³΄κ³ νλ‘μ νΈ μν©μ λ§κ² μ°νν λ°©λ²μ κ³ λ―Όνλ κ²μ μΆμ²νλ€.
κ·Έλ¦¬κ³ μ°νν λ€λ₯Έ μμ΄λμ΄λ κ²½νμ΄ μλ€λ©΄ 곡μ ν΄μ£Όλ©΄ λ무 κ°μ¬νκ² λ€..!
React Safari μ€λμ€ μ¬μ νΈλ¬λΈ μν
[JS] audio νκ·Έλ‘ bgm μ¬μ, play() failed because the user didn't interact with the document first μλ¬
New <video> Policies for iOS