Disguised Audio Player Works Perfectly in Firefox but not in Chrome
Asked Answered
M

2

6

I'm using the following HTML, CSS and Javascript to disguise a video player as an audio player.

It works perfectly in Firefox but in Chrome pressing the play button does not actually play anything.

What is preventing it from working in chrome and how can I get it to work in Chrome too?

Update:

I want to expand on a observation that I think maybe helpful (A special thank you to @AHaworth for making a similar observation in the comments):

If I remove iframe { display: none; } & the video player initially loads alongside the audio player, the audio player controls (still) has NO effect on the video player. However, if I click play on the video player and then hide it completely using CSS I am now able to fully control the video player through the audio player.. even though the video player is completely hidden. This observation is similar to @AHaworth in the comments "The error in the console that seems most relevant is the one saying that can't play the video because the user has not interacted." So the right approach is to force "interactivity" but how?

Check out the CodePen here.

let player;
const playBtn = document.getElementById('play');
const rewindBtn = document.getElementById('rewind');
const forwardBtn = document.getElementById('forward');
const progressBar = document.getElementById('progress-bar');
const currentTimeEl = document.getElementById('current-time');
const endTimeEl = document.getElementById('end-time');
const speedControl = document.getElementById('speed-control');
const volumeBtn = document.getElementById('volume-btn');
const volumeSlider = document.getElementById('volume-slider');
let duration = 0;

window.addEventListener('load', function() {
  player = new playerjs.Player(document.getElementById("bunny-stream-embed"));
  player.on('ready', () => {
    console.log('Player is ready');
    player.getDuration(d => {
      duration = d;
      endTimeEl.innerText = formatTime(duration);
      progressBar.max = duration;
    });

    // Set initial volume to 70%
    player.setVolume(70);
    volumeSlider.value = 70;
    updateVolumeIcon(0.7);

    // Set up event listeners after player is ready
    setupEventListeners();
  });
  player.on('timeupdate', (data) => {
    currentTimeEl.innerText = formatTime(data.seconds);
    progressBar.value = data.seconds;
  });
  player.on('play', () => {
    playBtn.innerHTML = '❚❚'; // Pause icon
  });
  player.on('pause', () => {
    playBtn.innerHTML = '►'; // Play icon
  });
});

function setupEventListeners() {
  playBtn.addEventListener('click', () => {
    player.getPaused(paused => {
      if (paused) {
        player.play();
      } else {
        player.pause();
      }
    });
  });
  rewindBtn.addEventListener('click', () => {
    player.getCurrentTime(currentTime => {
      player.setCurrentTime(Math.max(0, currentTime - 15));
    });
  });
  forwardBtn.addEventListener('click', () => {
    player.getCurrentTime(currentTime => {
      player.setCurrentTime(Math.min(duration, currentTime + 15));
    });
  });
  progressBar.addEventListener('input', (e) => {
    const time = parseFloat(e.target.value);
    player.setCurrentTime(time);
  });
  volumeSlider.addEventListener('input', (e) => {
    const volume = e.target.value;
    player.setVolume(volume);
    updateVolumeIcon(volume);
  });
  speedControl.addEventListener('click', () => {
    const speedOptions = [1, 1.25, 1.5, 2];
    let currentSpeed = parseFloat(speedControl.innerText.replace('x', ''));
    const newSpeed = speedOptions[(speedOptions.indexOf(currentSpeed) + 1) % speedOptions.length];
    speedControl.innerText = `${newSpeed}x`;
    player.setPlaybackRate(newSpeed);
  });
  volumeBtn.addEventListener('click', () => {
    player.getVolume(volume => {
      if (volume > 0) {
        player.setVolume(0);
        volumeSlider.value = 0;
      } else {
        player.setVolume(70);
        volumeSlider.value = 70;
      }
      updateVolumeIcon(volume > 0 ? 0 : 0.7);
    });
  });
}

function updateVolumeIcon(volume) {
  if (volume > 0.5) {
    volumeBtn.innerHTML = '🔊';
  } else if (volume > 0) {
    volumeBtn.innerHTML = '🔉';
  } else {
    volumeBtn.innerHTML = '🔇';
  }
}

function formatTime(seconds) {
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
body {
  font-family: Arial, sans-serif;
  background-color: #f0f0f0;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

iframe {
  display: none;
}

.audio-player {
  background-color: #1e3d59;
  color: white;
  width: 100%;
  max-width: 800px;
  border-radius: 10px;
  overflow: hidden;
}

.player-top {
  display: flex;
  align-items: center;
  padding: 20px;
}

.cover {
  width: 60px;
  height: 60px;
  border-radius: 5px;
  margin-right: 15px;
}

.info {
  flex-grow: 1;
}

.title {
  margin: 0;
  font-size: 18px;
}

.author {
  margin: 5px 0 0;
  font-size: 14px;
  opacity: 0.8;
}

.bookmark {
  background: none;
  border: none;
  color: white;
  font-size: 24px;
  cursor: pointer;
}

.player-bottom {
  background-color: #102c43;
  padding: 15px 20px;
}

.progress {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
}

#progress-bar {
  flex-grow: 1;
  margin: 0 10px;
  cursor: pointer;
}

.controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.control-btn {
  background: none;
  border: none;
  color: white;
  font-size: 16px;
  cursor: pointer;
}

.play-pause {
  font-size: 24px;
}

.playback-rate,
.volume-control {
  display: flex;
  align-items: center;
}

#volume-slider {
  width: 80px;
  margin-left: 10px;
}

input[type="range"] {
  -webkit-appearance: none;
  background: transparent;
}

input[type="range"]::-webkit-slider-runnable-track {
  width: 100%;
  height: 4px;
  background: #ffffff50;
  border-radius: 2px;
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  background: white;
  border-radius: 50%;
  cursor: pointer;
  margin-top: -4px;
}
    <script type="text/javascript" src="https:////assets.mediadelivery.net/playerjs/player-0.1.0.min.js"></script>
    <iframe id="bunny-stream-embed" src="https://iframe.mediadelivery.net/embed/197133/dc48a09e-d9bb-420a-83d7-72dc2304c034?autoplay=false&preload=true" width="720" height="400" frameborder="0" allow="autoplay"></iframe>
    
    <div class="audio-player">
        <div class="player-top">
            <img class="cover" src="https://images.blinkist.io/images/books/5bf9dc9c6cee070007cab481/1_1/470.jpg" alt="Book Cover">
            <div class="info">
                <h2 class="title">Atomic Habits</h2>
                <p class="author">James Clear</p>
            </div>
            <button class="bookmark">&#9733;</button>
        </div>
        <div class="player-bottom">
            <div class="progress">
                <span id="current-time">00:00</span>
                <input id="progress-bar" type="range" min="0" max="100" value="0">
                <span id="end-time">00:00</span>
            </div>
            <div class="controls">
                <button id="rewind" class="control-btn">&#8592; 15</button>
                <button id="play" class="control-btn play-pause">&#9658;</button>
                <button id="forward" class="control-btn">15 &#8594;</button>
                <div class="playback-rate">
                    <button id="speed-control" class="control-btn">1x</button>
                </div>
                <div class="volume-control">
                    <button id="volume-btn" class="control-btn">&#128266;</button>
                    <input type="range" id="volume-slider" min="0" max="100" value="70">
                </div>
            </div>
        </div>
    </div>
    
    <script src="script.js"></script>
Mischievous answered 25/9, 2024 at 6:5 Comment(18)
@KIKOSoftware I don't think it has anything do to with it. I think it has to do with: iframe { display: none; }Mischievous
Try and replace the display: none with one of the "screenreader-friendly" ways of hiding content - css-tricks.com/inclusively-hidden, sitelint.com/blog/…Combative
The error in the console that seems most relevant is the one saying that can't play the video because the user has not interacted. You need to work out how to get the user interaction to go through to the iframe - which it appears it does not in Chrome - maybe, but I'm not certain, because the display is none.Meredi
Do you have to have the video in an iframe as opposed to played directly?Meredi
@AHaworth Your observation seems correct "the error in the console that seems most relevant is the one saying that can't play the video because the user has not interacted.." I have made a similar observation myself: If I remove iframe { display: none; } & the video player loads alongside the audio player, the audio player has NO effect on the video player. However, if I click play on the video player and then hide it using CSS I am now able to fully control the video player through the audio player.. So it seems the right approach is to force "interactivity" but how?Mischievous
@Combative thanks for your response. I tried but it doesn't seem to work. I think this is because we need to somehow "stimulate" interactivity.. as @A Haworth suggested..Mischievous
@AHaworth "Do you have to have the video in an iframe as opposed to played directly" the video is being hosted on bunny.net while I believe they offer direct access to video (without iframe) we need DRM functionality. With Bunny.net it is built into the iframe. We don't want to roll out our own DRM solution.Mischievous
You can't (or shouldn't) simulate interactivity. The whole purpose of the user interaction requirement was to ensure that humans retained control over their computing environment. This code, while presumably benign in its effects, is still removing human control over their environment by not allowing them to control the video element themselves. I think even if you find a way today, that way might get blocked when someone finds out you've gotten around it.Amiens
this is happened becuse of CSP content-security-policy.com and its disallow elements that are not used at all on the site. as you says iframe { display: none; }Sattler
player.js hasn't been updated in 7 years and never went past v0.1; Is it the best choice of player?Abba
Can you please share the version in Chrome find issue, so I am helping usKetchan
I think there's a confusion between 'stimulate' and 'simulate' - as @Combative has pointed out, you shouldn't try to simulate interactivity (and actually, you can't in this case). It has to be a real user click on the right place in the iframe to get things started.Meredi
@A Haworth So are you essentially suggesting that it is not theoretically possible to disguise the video player as audio player since the controls won't work without a real click? BTW, for those who are wondering why I want to do this it is because the big streaming service providers (who provide DRM protection like VDOCipher or Bunny.net Stream) only stream via a native video player but I need to stream audio, not video. They allow you to upload audio but it is displayed like a video. So my only option is to make the video player look like an audio player.. I hope this makes sense.Mischievous
@UmeshSingh I have been testing it on Chrome Version 128 but I don't think the version makes a difference.Mischievous
@Abba I don't know if it is the best choice but I am streaming via VDOCipher or Bunny.net Stream so I don't get to choose which video player to use.. I use whatever they provide..Mischievous
@AlanMoore I'm not suggesting it's impossible, but I for one haven't worked out how to do it as bunny seems to take over all click handling immediately.Meredi
Seems like Bunny.net support should be engaged if not already. Also, perhaps they can advise on the possibility of using other players with their platform.Abba
@AHaworth Thanks for your reply. Can you clarify what you mean by "Bunny takes over click handling"? I was under the impression this issue had to do with the way Chrome handles it as the code works perfectly on Firefox.Mischievous
M
3

This is not a full answer,but I put the info here in case it can lead someone to a complete explanation and solution.

In the given codepen if I change the iframe CSS in the initial stylesheet to:

iframe {
  opacity: 0;
  z-index: 1;
  position: absolute;
}

and then where the play arrow gets changed to the pause icon I change the display property of the iframe to 'none' the player works.

player.on('play', () => {
    playBtn.innerHTML = '&#10074;&#10074;'; // Pause icon
   document.getElementById('bunny-stream-embed').style.display = 'none';
});

or at least, gives the appearance of working, because of course the first click was direct on the iframe rather than one specific spot on the control panel.

Meredi answered 30/9, 2024 at 22:40 Comment(4)
Thanks so much for your answer. I upvoted it. This is an excellent starting point. Can this technique also be used to set the start / end time (e.g, 00:00 - 00:22) and control playback speed (1x, 1.25x, 1.5x)? As of now it seems only the play / pause button is working as well as the ability to rewind/fast forward the audio. If I understand correctly this solution works because the iframe is actually present, but invisible, and clicking the play button on the audio player is basically like clicking the play button on the video frame?Mischievous
I thought all the buttons worked once the initial play (ie anywhere on the iframe) had been clicked for the first time. I’ll take a look. I don’t think they can be made to work before that fist click, which results in playing, though.Meredi
@AHaworth added new bountyChit
@AHaworth Any ideas? Thanks :)Mischievous
Y
0

2 ERRORS IN YOU CODE

  1. I noticed that in this piece of code:

         <iframe id="bunny-stream-embed" src="https://iframe.mediadelivery.net/embed/197133/dc48a09e-d9bb-420a-83d7-
       72dc2304c034?autoplay=false&preload=true" width="720" height="400" frameborder="0" allow="autoplay"></iframe>
    

    I know that autoplay doesn't work. But for newbies or beginners, this is quite confusing: because you are disallowing autoplay and then re-allowing it. So, you have to remove allow="autoplay".

  2. I saw this error when I tested the speedControl button:

    Uncaught TypeError: player.setPlaybackRate is not a function at 
    https://cdpn.io/cpe/boomboom/pen.js?key=pen.js-1bff938b-eb47-861c-0d95- 
    a4f5ea83bed6:77
    

    According to this MDN article, this error appears because the playerjs library may not support the setPlaybackRate function or it may not exist. Try to check the library documentation or try to use the below alternative method:

       speedControl.addEventListener('click', () => {
             const speedOptions = [1, 1.25, 1.5, 2];
             let currentSpeed = parseFloat(speedControl.innerText.replace('x', ''));
             const newSpeed = speedOptions[(speedOptions.indexOf(currentSpeed) + 1) %   
    speedOptions.length];
             speedControl.innerText = `${newSpeed}x`;
             // Alternative method to set playback rate
             player.setPlaybackRate(newSpeed); // Ensure this method is supported
           });
    

SOLUTION

Your issue is that Chrome requires user interaction to play media. You can add a user interaction event before hiding the video player:

playBtn.addEventListener('click', () => {
  player.getPaused(paused => {
    if (paused) {
      player.play();
      document.getElementById('bunny-stream-embed').style.display = 'none';
    } else {
      player.pause();
    }
  });
});

For more help, you can consult the following sources:

Yim answered 4/10, 2024 at 18:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.