rshinyhtml5-audioaudio-playerresponse-headers

HTML5 Audio player in R Shiny app goes to beginning of track when setting currentTime


I have created an audio player using R shiny which creates an audio player like this:

  tags$audio(
    src = "song.mp3",  
    type = "audio/mp3",
    controls = "controls"         
  )

I also have controls in the app that change the playback position using runjs to set the currentTime attribute of the audio player which changes the time of the playback for the audio file. In general it works well however sometimes, I set currentTime to a point in the middle of the track, but it just goes back to the beginning of the track. Once this behaviour starts it doesn't stop (i.e. every time I change the currentTime it just keeps going back to the beginning of the track) until I restart the app. This seems to be a known issue with the HTML audio object as documented here here and in other places. The fix it seems is to set response headers as follows:

accept-ranges: bytes
Content-Length: BYTE_LENGTH_OF_YOUR_FILE
Content-Range: bytes 0-BYTE_LENGTH_OF_YOUR_FILE/BYTE_LENGTH_OF_YOUR_FILE
content-type: audio/mp3

My question is, how do I set the response header in an R Shiny app. I don't know where I would add this in the app. Do I set it at the launch of the app or when the audio player is being created?

Sorry I don't have a REPREX for this. It isn't really a bug fix I'm after. I'm just looking for a method for setting/specifying the response header as above. Also, in the link above, it provides an image of the response headers. I have no idea how to find this information and it would be good to be able to view and check this.

EDIT

I now have a REPREX for this problem. I have tried it on three machines (all Windows 11, tested on the Shiny browser and in Chrome). On two of them the behavior was consistent (the button makes the track go to the beginning instead of 10 seconds in) on the third, it failed the first time it was run and then started working with desired bahviour. I downloaded the mp3 from here and put it in a www folder next to the app.R file.

library(shiny)

# Define the local audio file path
local_audio_file <- "audio_db6591201e.mp3"

ui <- fluidPage(
  tags$h2("Local Audio Player"),
  
  # Audio player
  tags$audio(id = "audioPlayer", 
             src = local_audio_file,  # Use the local file path
             type = "audio/mpeg", 
             controls = TRUE),
  
  # Progress bar
  tags$progress(id = "progressBar", value = 0, max = 100, style = "width: 100%;"),
  
  # Button to skip to 10 seconds
  actionButton("skipButton", "Skip to 10 seconds")
)

server <- function(input, output, session) {
  # Initialize the audio progress
  observe({
    session$sendCustomMessage(type = "initAudio", message = NULL)
  })
  
  # Skip to 10 seconds when button is clicked
  observeEvent(input$skipButton, {
    session$sendCustomMessage(type = "skipAudio", message = 10)
  })
}

# JavaScript code for audio control
jsCode <- "
Shiny.addCustomMessageHandler('initAudio', function(message) {
  var audio = document.getElementById('audioPlayer');
  var progressBar = document.getElementById('progressBar');
  
  audio.ontimeupdate = function() {
    var percentage = (audio.currentTime / audio.duration) * 100;
    progressBar.value = percentage;
  };
});

Shiny.addCustomMessageHandler('skipAudio', function(seconds) {
  var audio = document.getElementById('audioPlayer');
  audio.currentTime = seconds;
});
"

# Run the Shiny app
shinyApp(
  ui = tagList(
    tags$head(tags$script(HTML(jsCode))),
    ui
  ), 
  server = server
)

Solution

  • I was able to reproduce your bug with Chrome in Linux.

    The answer here did help but the .b64EncodeFile() function proposed there to encode the audio file as base64 is not available in the markdown package anymore.

    Instead, we can use the xfun::base64_uri() function. Once encoded as base64, the browser is apparently able to access the correct headers of the file.

    The bug was removed with this:

    local_audio_file <- "www/audio_db6591201e.mp3"
    as_b64 = xfun::base64_uri(local_audio_file)
    
    ui <- fluidPage(
      tags$h2("Local Audio Player"),
      
      # Audio player
      tags$audio(id = "audioPlayer", 
                 src = as_b64,  # Use the base64 encoded file
                 type = "audio/mpeg", 
                 controls = TRUE),
    
    # ...