shaka-project / shaka-player

JavaScript player library / DASH & HLS client / MSE-EME player
Apache License 2.0
7.21k stars 1.34k forks source link

Web audio questions in safari #3616

Closed baehongjun0212 closed 3 years ago

baehongjun0212 commented 3 years ago

Have you read the Tutorials? Yes

Have you read the FAQ and checked for duplicate open issues? Yes; #2595 #3484 #3563

What version of Shaka Player are you using? 3.2.0

Please ask your question The sound of the video was visualized as a canvas using sample code from #2595. But in safari, they don't draw anything on the canvas. How can we solve this? 스크린샷 2021-09-01 오후 5 16 06

I'll attach my code.

var ua = navigator.userAgent.toLowerCase(); 
  if (ua.indexOf('safari') != -1) { 
    if (ua.indexOf('chrome') > -1) {
      console.log("Access by Chrome..")
      manifestUri = "https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd" 
    } else {
      console.log("Access by Safari..")
      manifestUri = "https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/m3u8s/11331.m3u8"
  }
}

const canvas1 = document.getElementById('canvas1');
const canvas2 = document.getElementById('canvas2');

var context1 = new (window.AudioContext || window.webkitAudioContext)();
var context2 = new (window.AudioContext || window.webkitAudioContext)();

const source1 = context1.createMediaElementSource(video1);
const source2 = context2.createMediaElementSource(video2);

const splitter1 = context1.createChannelSplitter(2); 
const splitter2 = context2.createChannelSplitter(2);

source1.connect(splitter1);
source2.connect(splitter2);

const dataSize = 2048; 
const a1 = context1.createAnalyser();
const a2 = context2.createAnalyser();

const merger1 = context1.createChannelMerger();
const merger2 = context2.createChannelMerger();

const button1 = document.getElementById('button1');
const button2 = document.getElementById('button2');

$("#button1").on("click", function () {
  if ( test == false) { 
    console.log("info. User1 Sound ON");
    $(this).text("USER1 OFF");
    a1.connect(merger1, /* channel */ 0, /* destinationChannel */ 0);
    merger1.connect(context1.destination);
  } else { 
    console.log("info. User1 Sound OFF");
    $(this).text("USER1 ON");
    a1.disconnect(merger1, /* channel */ 0, /* destinationChannel */ 0);
    merger1.disconnect(context1.destination);
  }
  test = test?false:true;
});

$("#button2").on("click", function () {
  if ( test == false) { 
    console.log("info. User2 Sound ON");
    $(this).text("USER2 OFF");
    a2.connect(merger2, /* channel */ 0, /* destinationChannel */ 0); 
    merger2.connect(context2.destination);
  } else { 
    console.log("info. User2 Sound OFF");
    $(this).text("USER2 ON");
    a2.disconnect(merger2, /* channel */ 0, /* destinationChannel */ 0); 
    merger2.disconnect(context2.destination);
  }
  test = test?false:true;
});

function initApp() {
  shaka.polyfill.installAll();
  if (shaka.Player.isBrowserSupported()) {
    initPlayer();
  } else {
    console.error('Browser not supported!');
  }

  const gainInput1 = createRangeInput();  
  const gainInput2 = createRangeInput();  

  const gain1 = context1.createGain();
  gainInput1.oninput = () => gain1.gain.value = gainInput1.value; 
  splitter1.connect(gain1, /* channel */ 0);

  const gain2 = context2.createGain();
  gainInput2.oninput = () => gain2.gain.value = gainInput2.value; 
  splitter2.connect(gain2, /* channel */ 0);

  const data1 = new Uint8Array(dataSize);
  a1.fftSize = dataSize;
  gain1.connect(a1);

  const data2 = new Uint8Array(dataSize);
  a2.fftSize = dataSize;
  gain2.connect(a2);

  const drawingContext1 = canvas1.getContext('2d'); 
  const drawingContext2 = canvas2.getContext('2d'); 
  requestAnimationFrame(draw); // draw 함수를 loop 돌려 캔버스에 프레임 출력

  function createRangeInput() {
    const input = document.createElement('input');
    input.type = 'range';
    input.max = 10; 
    input.min = 0;
    input.value = 5; 
    input.step = 0.001;
    return input;
  }

  function maxVolume(data) {
    return Math.max.apply(null,
        Array.from(data).map((x) => Math.abs((x - 128) / 128)));
  }

  function draw() {
    requestAnimationFrame(draw);
    a1.getByteTimeDomainData(data1);
    a2.getByteTimeDomainData(data2);

    const max1 = maxVolume(data1);
    const max2 = maxVolume(data2);

    drawingContext1.clearRect(0, 0, canvas1.width, canvas1.height);
    drawingContext1.beginPath(); 
    drawingContext1.moveTo(10, 50); 
    drawingContext1.lineTo(300, 50); 
    drawingContext1.lineWidth = 2;
    drawingContext1.strokeStyle = 'white';
    drawingContext1.setLineDash([3]);
    drawingContext1.stroke();
    drawingContext1.beginPath(); 
    drawingContext1.moveTo(10, 105); 
    drawingContext1.lineTo(300, 105);
    drawingContext1.stroke();
    drawingContext1.fillStyle = 'skyblue';
    drawingContext1.fillRect(0, canvas1.height, canvas1.width, -max1 * canvas1.height * 2.5);

    drawingContext2.clearRect(0, 0, canvas2.width, canvas2.height);
    drawingContext2.beginPath(); 
    drawingContext2.moveTo(10, 50); 
    drawingContext2.lineTo(300, 50); 
    drawingContext2.lineWidth = 2; 
    drawingContext2.strokeStyle = 'white';
    drawingContext2.setLineDash([3]);
    drawingContext2.stroke();
    drawingContext2.beginPath(); 
    drawingContext2.moveTo(10, 105); 
    drawingContext2.lineTo(300, 105);
    drawingContext2.stroke();
    drawingContext2.fillStyle = 'skyblue';
    drawingContext2.fillRect(0, canvas2.height, canvas2.width, -max2 * canvas2.height * 2.5);
  }
}

async function initPlayer() {
  const video1 = document.getElementById('video1');
  const video2 = document.getElementById('video2');

  const player1 = new shaka.Player(video1);
  const player2 = new shaka.Player(video2);

  window.player1 = player1;
  window.player2 = player2;

  player1.addEventListener('error', onErrorEvent);
  player2.addEventListener('error', onErrorEvent);

  try {
    await player1.load(manifestUri);
    await player2.load(manifestUri); 
    console.log('The video has now been loaded!');

  } catch (e) {
    onError(e);
  }
}

function onErrorEvent(event) {
  onError(event.detail);
}

function onError(error) {
  console.error('Error code', error.code, 'object', error);
}
document.addEventListener('DOMContentLoaded', initApp);
joeyparrish commented 3 years ago

My only guess is that you're using HLS for Safari, so it may be going through the browser's native HLS implementation instead of using MediaSource. I could imagine an implementation of HLS in the browser that could bypass WebAudio.

Try this configuration in Shaka:

player.configure('streaming.useNativeHlsOnSafari', false);

This will prefer MediaSource to native HLS. On iOS, we will still be forced to use native HLS, so I don't expect this to be effective on iOS. But it may work in desktop Safari.

Does this help?

shaka-bot commented 3 years ago

@baehongjun0212 Does this answer all your questions? Can we close the issue?

shaka-bot commented 3 years ago

Closing due to inactivity. If this is still an issue for you or if you have further questions, you can ask us to reopen or have the bot reopen it by including @shaka-bot reopen in a comment.