/**
 * When media changes its audible state, the sound indicator should be
 * updated as well, which should appear only when web audio is audible.
 */
add_task(async function testUpdateSoundIndicatorWhenMediaPlaybackChanges() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaPlaybackDocument(tab, "audio.ogg");

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear when audio stops playing`);
  await pauseMedia(tab);
  await waitForTabSoundIndicatorDisappears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testUpdateSoundIndicatorWhenMediaBecomeSilent() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaPlaybackDocument(tab, "audioEndedDuringPlaying.webm");

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear when audio becomes silent`);
  await waitForTabSoundIndicatorDisappears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testSoundIndicatorWouldWorkForMediaWithoutPreload() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaPlaybackDocument(tab, "audio.ogg", { preload: "none" });

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear when audio stops playing`);
  await pauseMedia(tab);
  await waitForTabSoundIndicatorDisappears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testSoundIndicatorShouldDisappearAfterTabNavigation() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaPlaybackDocument(tab, "audio.ogg");

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear after navigating tab to blank page`);
  BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:blank");
  await waitForTabSoundIndicatorDisappears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testSoundIndicatorForAudioStream() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaStreamPlaybackDocument(tab);

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear when audio stops playing`);
  await pauseMedia(tab);
  await waitForTabSoundIndicatorDisappears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testPerformPlayOnMediaLoadingNewSource() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaPlaybackDocument(tab, "audio.ogg");

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear when audio stops playing`);
  await pauseMedia(tab);
  await waitForTabSoundIndicatorDisappears(tab);

  info(`reset media src and play it again should make sound indicator appear`);
  await assignNewSourceForAudio(tab, "audio.ogg");
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testSoundIndicatorShouldDisappearWhenAbortingMedia() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab();
  await initMediaPlaybackDocument(tab, "audio.ogg");

  info(`sound indicator should appear when audible audio starts playing`);
  await playMedia(tab);
  await waitForTabSoundIndicatorAppears(tab);

  info(`sound indicator should disappear when aborting audio source`);
  await assignNewSourceForAudio(tab, "");
  await waitForTabSoundIndicatorDisappears(tab);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testNoSoundIndicatorForMediaWithoutAudioTrack() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab({ needObserver: true });
  await initMediaPlaybackDocument(tab, "noaudio.webm", { createVideo: true });

  info(`no sound indicator should show for playing media without audio track`);
  await playMedia(tab, { resolveOnTimeupdate: true });
  ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testSoundIndicatorWhenChangingMediaMuted() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab({ needObserver: true });
  await initMediaPlaybackDocument(tab, "audio.ogg", { muted: true });

  info(`no sound indicator should show for playing muted media`);
  await playMedia(tab, { resolveOnTimeupdate: true });
  ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");

  info(`unmuted media should make sound indicator appear`);
  await Promise.all([
    waitForTabSoundIndicatorAppears(tab),
    updateMedia(tab, { muted: false }),
  ]);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

add_task(async function testSoundIndicatorWhenChangingMediaVolume() {
  info("create a tab loading media document");
  const tab = await createBlankForegroundTab({ needObserver: true });
  await initMediaPlaybackDocument(tab, "audio.ogg", { volume: 0.0 });

  info(`no sound indicator should show for playing volume zero media`);
  await playMedia(tab, { resolveOnTimeupdate: true });
  ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");

  info(`unmuted media by setting volume should make sound indicator appear`);
  await Promise.all([
    waitForTabSoundIndicatorAppears(tab),
    updateMedia(tab, { volume: 1.0 }),
  ]);

  info("remove tab");
  BrowserTestUtils.removeTab(tab);
});

/**
 * Following are helper functions
 */
function initMediaPlaybackDocument(
  tab,
  fileName,
  { preload, createVideo, muted = false, volume = 1.0 } = {}
) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [fileName, preload, createVideo, muted, volume],
    // eslint-disable-next-line no-shadow
    async (fileName, preload, createVideo, muted, volume) => {
      if (createVideo) {
        content.media = content.document.createElement("video");
      } else {
        content.media = content.document.createElement("audio");
      }
      if (preload) {
        content.media.preload = preload;
      }
      content.media.muted = muted;
      content.media.volume = volume;
      content.media.src = fileName;
    }
  );
}

function initMediaStreamPlaybackDocument(tab) {
  return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
    content.media = content.document.createElement("audio");
    content.media.srcObject =
      new content.AudioContext().createMediaStreamDestination().stream;
  });
}

function playMedia(tab, { resolveOnTimeupdate } = {}) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [resolveOnTimeupdate],
    // eslint-disable-next-line no-shadow
    async resolveOnTimeupdate => {
      await content.media.play();
      if (resolveOnTimeupdate) {
        await new Promise(r => (content.media.ontimeupdate = r));
      }
    }
  );
}

function pauseMedia(tab) {
  return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
    content.media.pause();
  });
}

function assignNewSourceForAudio(tab, fileName) {
  // eslint-disable-next-line no-shadow
  return SpecialPowers.spawn(tab.linkedBrowser, [fileName], async fileName => {
    content.media.src = "";
    content.media.removeAttribute("src");
    content.media.src = fileName;
  });
}

function updateMedia(tab, { muted, volume } = {}) {
  return SpecialPowers.spawn(
    tab.linkedBrowser,
    [muted, volume],
    // eslint-disable-next-line no-shadow
    (muted, volume) => {
      if (muted != undefined) {
        content.media.muted = muted;
      }
      if (volume != undefined) {
        content.media.volume = volume;
      }
    }
  );
}
