ashbaldry / howler Goto Github PK
View Code? Open in Web Editor NEWShiny extension of howler.js
Home Page: https://ashbaldry.github.io/howler
License: Other
Shiny extension of howler.js
Home Page: https://ashbaldry.github.io/howler
License: Other
Using the filenames as track titles doesn't always work well. For example, I have a use case with long URLs with numerical ids as filenames.
It'd be nicer if we could pass track titles alongside the track files. A natural way to do it would be as the names
of the files
vector - defaulting to the current behaviour if the files are unnamed. This would be analogous to e.g. the choices
argument of shiny::selectInput
.
Hi Ashley - I've been testing out the new htmlwidget implementation and it's really good!
There's a small bug with the track name when creating a howler with a single track. Basically the track name doesn't get stored in an array in the JS when there's only one track. This means that track_names[current_track]
returns the first letter instead of the first track title! Also it breaks addTrack
because track_names.push()
causes an error.
This is down to this bit of JS:
if (Array.isArray(x.tracks)) {
tracks = x.tracks;
track_names = x.names;
if (x.formats) {
track_formats = x.formats;
}
} else if (x.tracks) {
tracks = [x.tracks];
track_names = [x.names];
if (x.formats) {
track_formats = [x.formats];
}
} else {
tracks = [];
track_names = [];
}
It turns out that when there's a single track, the JS receives options like: tracks: Array(1), names: 'test', ...
. So tracks
is an array but names
isn't.
I am using jsonlite 1.8.0
, maybe this is version-sensitive.
Because of the way the howlerPlayers are detected by the JS code when shiny first connects, any player added later (e.g. through UI injection) won't reveive the messages from shiny.
One easy way to fix this would be to add a refreshPlayers()
function which would cause the howlerPlayers
to be re-detected in JS. One would have to call that function after adding a player dynamically. It's a bit clunky though.
(Side note: I don't actually need that feature so I probably won't address it in my fork.)
Borrowing from daattali/shinyjs#110
It would be great if the following piece of code:
library(shiny)
library(howler)
ui <- fluidPage(
howler(tracks = NULL, elementId = "sound"),
actionButton("play", "Play sound")
)
server <- function(input, output, session) {
observeEvent(input$play, {
addTrack("sound", <file_to_add>, play_track = TRUE)
})
}
shinyApp(ui = ui, server = server)
Could be reduced to the following:
library(shiny)
library(howler)
ui <- fluidPage(
actionButton("play", "Play sound")
)
server <- function(input, output, session) {
observeEvent(input$play, {
playSound(<file_to_add>)
})
}
shinyApp(ui = ui, server = server)
Is this something you'd support?
Thanks for sharing your package (MIT license is โค๏ธ), it's going to be super useful!
I need to make a number of changes to make it work for my use case, which I'll implement in my fork. I'm going to open a few issues just to document what I intend to change.
Currently, HTTP URLs with query parameters are not handled correctly and cannot be played.
These are URLs like:
http://www.example.com/files/audio.mp3?a=1&b=2
Use case: tracks in a private AWS S3 bucket are accessible using presigned URLs which have signature details in such query parameters.
Create a function synonymous to addTrack
where the user can delete a track from a howler player from the server-side.
Validation will be needed (possibly some feedback) whether or not the track exists, and if playing should the player stop or skip to next track?
Thanks a lot for all the changes you've implemented recently - as far as I can tell, everything is working great!
Could you please make a github release (or at least just a tag v0.1.2) to simplify deployment? This way we can install the package from the "known good" ashbaldry/[email protected]
.
Are you planning a CRAN release soon? It'd be great but I appreciate it's a bit of work - happy to help with it if needed :)
We have a server-side function to play and pause, we could also have one to toggle play.
#' @rdname howlerServer
#' @export
togglePlayHowl <- function(id, session = getDefaultReactiveDomain()) {
message_name <- paste0("togglePlayHowler_", session$ns(id))
session$sendCustomMessage(message_name, id)
}
Shiny.addCustomMessageHandler(`togglePlayHowler_${el.id}`, function(id) {
sound.playing() ? sound.pause() : sound.play();
});
Just for convenience!
The following doesn't work in {howler}
library(shiny)
library(howler)
audio_files_dir <- system.file("examples/_audio", package = "howler")
addResourcePath("sample_audio", audio_files_dir)
audio_files <- file.path("sample_audio", list.files(audio_files_dir, ".mp3$"))
ui <- fluidPage(
title = "howler Basic Example",
h1("howler Basic Example"),
howlerOutput("howlX"),
actionButton("button", "Play")
)
server <- function(input, output, session) {
output$howlX <- renderHowler({
req(input$button)
howler::howler(audio_files[1L], options = list(autoplay = TRUE))
})
}
shinyApp(ui, server)
but including a dummy howler()
in the UI allows it to work. Looks like the custom widget tag is causing issues.
Trying to use {howler}
for Way to stop any previously playing sounds (base64) in javascript before playing next sound and it didn't work, however howler.js does work with a solution. Need to enable this to work.
Example sound:
"data:audio/wav;base64,UklGRhwMAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAZGF0Ya4LAACAgICAgICAgICAgICAgICAgICAgICAgICAf3hxeH+AfXZ1eHx6dnR5fYGFgoOKi42aloubq6GOjI2Op7ythXJ0eYF5aV1AOFFib32HmZSHhpCalIiYi4SRkZaLfnhxaWptb21qaWBea2BRYmZTVmFgWFNXVVVhaGdbYGhZbXh1gXZ1goeIlot1k6yxtKaOkaWhq7KonKCZoaCjoKWuqqmurK6ztrO7tbTAvru/vb68vbW6vLGqsLOfm5yal5KKhoyBeHt2dXBnbmljVlJWUEBBPDw9Mi4zKRwhIBYaGRQcHBURGB0XFxwhGxocJSstMjg6PTc6PUxVV1lWV2JqaXN0coCHhIyPjpOenqWppK6xu72yxMu9us7Pw83Wy9nY29ve6OPr6uvs6ezu6ejk6erm3uPj3dbT1sjBzdDFuMHAt7m1r7W6qaCupJOTkpWPgHqAd3JrbGlnY1peX1hTUk9PTFRKR0RFQkRBRUVEQkdBPjs9Pzo6NT04Njs+PTxAPzo/Ojk6PEA5PUJAQD04PkRCREZLUk1KT1BRUVdXU1VRV1tZV1xgXltcXF9hXl9eY2VmZmlna3J0b3F3eHyBfX+JgIWJiouTlZCTmpybnqSgnqyrqrO3srK2uL2/u7jAwMLFxsfEv8XLzcrIy83JzcrP0s3M0dTP0drY1dPR1dzc19za19XX2dnU1NjU0dXPzdHQy8rMysfGxMLBvLu3ta+sraeioJ2YlI+MioeFfX55cnJsaWVjXVlbVE5RTktHRUVAPDw3NC8uLyknKSIiJiUdHiEeGx4eHRwZHB8cHiAfHh8eHSEhISMoJyMnKisrLCszNy8yOTg9QEJFRUVITVFOTlJVWltaXmNfX2ZqZ21xb3R3eHqAhoeJkZKTlZmhpJ6kqKeur6yxtLW1trW4t6+us7axrbK2tLa6ury7u7u9u7vCwb+/vr7Ev7y9v8G8vby6vru4uLq+tri8ubi5t7W4uLW5uLKxs7G0tLGwt7Wvs7avr7O0tLW4trS4uLO1trW1trm1tLm0r7Kyr66wramsqaKlp52bmpeWl5KQkImEhIB8fXh3eHJrbW5mYGNcWFhUUE1LRENDQUI9ODcxLy8vMCsqLCgoKCgpKScoKCYoKygpKyssLi0sLi0uMDIwMTIuLzQ0Njg4Njc8ODlBQ0A/RUdGSU5RUVFUV1pdXWFjZGdpbG1vcXJ2eXh6fICAgIWIio2OkJGSlJWanJqbnZ2cn6Kkp6enq62srbCysrO1uLy4uL+/vL7CwMHAvb/Cvbq9vLm5uba2t7Sysq+urqyqqaalpqShoJ+enZuamZqXlZWTkpGSkpCNjpCMioqLioiHhoeGhYSGg4GDhoKDg4GBg4GBgoGBgoOChISChISChIWDg4WEgoSEgYODgYGCgYGAgICAgX99f398fX18e3p6e3t7enp7fHx4e3x6e3x7fHx9fX59fn1+fX19fH19fnx9fn19fX18fHx7fHx6fH18fXx8fHx7fH1+fXx+f319fn19fn1+gH9+f4B/fn+AgICAgH+AgICAgIGAgICAgH9+f4B+f35+fn58e3t8e3p5eXh4d3Z1dHRzcXBvb21sbmxqaWhlZmVjYmFfX2BfXV1cXFxaWVlaWVlYV1hYV1hYWVhZWFlaWllbXFpbXV5fX15fYWJhYmNiYWJhYWJjZGVmZ2hqbG1ub3Fxc3V3dnd6e3t8e3x+f3+AgICAgoGBgoKDhISFh4aHiYqKi4uMjYyOj4+QkZKUlZWXmJmbm52enqCioqSlpqeoqaqrrK2ur7CxsrGys7O0tbW2tba3t7i3uLe4t7a3t7i3tre2tba1tLSzsrKysbCvrq2sq6qop6alo6OioJ+dnJqZmJeWlJKSkI+OjoyLioiIh4WEg4GBgH9+fXt6eXh3d3V0c3JxcG9ubWxsamppaWhnZmVlZGRjYmNiYWBhYGBfYF9fXl5fXl1dXVxdXF1dXF1cXF1cXF1dXV5dXV5fXl9eX19gYGFgYWJhYmFiY2NiY2RjZGNkZWRlZGVmZmVmZmVmZ2dmZ2hnaGhnaGloZ2hpaWhpamlqaWpqa2pra2xtbGxtbm1ubm5vcG9wcXBxcnFycnN0c3N0dXV2d3d4eHh5ent6e3x9fn5/f4CAgIGCg4SEhYaGh4iIiYqLi4uMjY2Oj5CQkZGSk5OUlJWWlpeYl5iZmZqbm5ybnJ2cnZ6en56fn6ChoKChoqGio6KjpKOko6SjpKWkpaSkpKSlpKWkpaSlpKSlpKOkpKOko6KioaKhoaCfoJ+enp2dnJybmpmZmJeXlpWUk5STkZGQj4+OjYyLioqJh4eGhYSEgoKBgIB/fn59fHt7enl5eHd3dnZ1dHRzc3JycXBxcG9vbm5tbWxrbGxraWppaWhpaGdnZ2dmZ2ZlZmVmZWRlZGVkY2RjZGNkZGRkZGRkZGRkZGRjZGRkY2RjZGNkZWRlZGVmZWZmZ2ZnZ2doaWhpaWpra2xsbW5tbm9ub29wcXFycnNzdHV1dXZ2d3d4eXl6enp7fHx9fX5+f4CAgIGAgYGCgoOEhISFhoWGhoeIh4iJiImKiYqLiouLjI2MjI2OjY6Pj46PkI+QkZCRkJGQkZGSkZKRkpGSkZGRkZKRkpKRkpGSkZKRkpGSkZKRkpGSkZCRkZCRkI+Qj5CPkI+Pjo+OjY6Njo2MjYyLjIuMi4qLioqJiomJiImIh4iHh4aHhoaFhoWFhIWEg4SDg4KDgoKBgoGAgYCBgICAgICAf4CAf39+f35/fn1+fX59fHx9fH18e3x7fHt6e3p7ent6e3p5enl6enl6eXp5eXl4eXh5eHl4eXh5eHl4eXh5eHh3eHh4d3h4d3h3d3h4d3l4eHd4d3h3eHd4d3h3eHh4eXh5eHl4eHl4eXh5enl6eXp5enl6eXp5ent6ent6e3x7fHx9fH18fX19fn1+fX5/fn9+f4B/gH+Af4CAgICAgIGAgYCBgoGCgYKCgoKDgoOEg4OEg4SFhIWEhYSFhoWGhYaHhoeHhoeGh4iHiIiHiImIiImKiYqJiYqJiouKi4qLiouKi4qLiouKi4qLiouKi4qLi4qLiouKi4qLiomJiomIiYiJiImIh4iIh4iHhoeGhYWGhYaFhIWEg4OEg4KDgoOCgYKBgIGAgICAgH+Af39+f359fn18fX19fHx8e3t6e3p7enl6eXp5enl6enl5eXh5eHh5eHl4eXh5eHl4eHd5eHd3eHl4d3h3eHd4d3h3eHh4d3h4d3h3d3h5eHl4eXh5eHl5eXp5enl6eXp7ent6e3p7e3t7fHt8e3x8fHx9fH1+fX59fn9+f35/gH+AgICAgICAgYGAgYKBgoGCgoKDgoOEg4SEhIWFhIWFhoWGhYaGhoaHhoeGh4aHhoeIh4iHiIeHiIeIh4iHiIeIiIiHiIeIh4iHiIiHiIeIh4iHiIeIh4eIh4eIh4aHh4aHhoeGh4aHhoWGhYaFhoWFhIWEhYSFhIWEhISDhIOEg4OCg4OCg4KDgYKCgYKCgYCBgIGAgYCBgICAgICAgICAf4B/f4B/gH+Af35/fn9+f35/fn1+fn19fn1+fX59fn19fX19fH18fXx9fH18fXx9fH18fXx8fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x8e3x7fHt8e3x7fHx8fXx9fH18fX5+fX59fn9+f35+f35/gH+Af4B/gICAgICAgICAgICAgYCBgIGAgIGAgYGBgoGCgYKBgoGCgYKBgoGCgoKDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KCgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGBgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCAgICBgIGAgYCBgIGAgYCBgIGAgYCBgExJU1RCAAAASU5GT0lDUkQMAAAAMjAwOC0wOS0yMQAASUVORwMAAAAgAAABSVNGVBYAAABTb255IFNvdW5kIEZvcmdlIDguMAAA"
First of all, great package! It's been quite helpful for understanding how to manipulate the playback of audio files.
I noticed that the tags$audio
option had the additional controls of downloading the audio and altering the playback speed:
audio$tags Default Preview |
audio$tags Options |
---|---|
I was looking into modifying the widget to access these controls, but just wanted to reach out and see if you had any plans to add the playback speed feature to the main howler
widget?
In various cases, it'd be useful to create a player with an empty playlist. In my use case, the playlist is constructed later and I have no tracks when the app starts.
One option might be to create the player later, once the first track comes in, but this is is not nice for 2 reasons:
For these reasons, we should be able to create a howlerPlayer without files (files = NULL
or files = list()
or files = character(0)
).
It might not be straightforward since we can't create a howl without a track, so effectively the player
element of the howlerPlayer
would have to be empty, and that is sure to cause some issues with play, plause, etc...
In the meantime, I'm using files = " "
(not an empty string as that wouldn't work) to get the player started with a dummy track and "invisible" title and that works ok. This empty track stays in the playlist though which is not ideal.
Only seems to happen before changing tracks, unable to find why it returns to the start.
Simple reprex:
library(shiny)
library(howler)
howler_example_dir <- system.file("examples/www/audio", package = "howler")
addResourcePath("audio_files", howler_example_dir)
howler_example_file <- list.files(howler_example_dir, ".mp3$")[1]
ui <- fluidPage(
useHowlerJS(),
howlerPlayer("sound", file.path("audio_files", howler_example_file)),
playPauseButton("sound"),
textOutput("seek")
)
server <- function(input, output, session) {
output$seek <- renderText(input$sound_seek)
}
shinyApp(ui, server)
changeTrack()
has a peculiar behaviour: it expects a file name rather than a file path.
I don't know if it's a bug or if it was on purpose. Perhaps it's for dealing with the case where multiple files are provided for the same track? But these files probably have different extensions, and the extension is part of the file name so it wouldn't help.
This is a bit strange because the howlerPlayer
is constructed with file paths, and the internal playlist is composed of file paths. It also means that changeTrack
cannot handle files with the same name in different directories, since we have to strip the directory structure to use it.
Here's an illustration.
library(shiny)
library(howler)
audio_files_dir <- system.file("examples/_audio", package = "howler")
addResourcePath("sample_audio", audio_files_dir)
audio_files <- file.path("sample_audio", list.files(audio_files_dir, ".mp3$"))
ui <- fluidPage(
useHowlerJS(),
h3("howler changeTrack example"),
howlerPlayer("sound", audio_files),
howlerPlayPauseButton("sound"),
howlerVolumeSlider("sound"),
howlerCurrentTrack("sound"),
tags$br(),
selectInput("selected_track", label = "Select a track", choices = audio_files)
)
server <- function(input, output, session) {
observeEvent(input$selected_track, {
# basename is required here, otherwise it won't work
changeTrack(session, "sound", basename(input$selected_track))
})
}
shinyApp(ui, server)
The example raises an interesting question: what should be the unique identifier of a track? Using the full file path to the audio file makes sense, but it's not necessarily user friendly - and there's always the case where multiple audio files are provided for the same track...
Perhaps changeTrack
could take either a file (full file path, matching the internal playlist) or an index (1-based in R, converted to 0-based in JS) so it's easy to move through a playlist.
Hey Ash - I'm back :)
Adding a track with a name doesn't work because Howl expects a string URL and not an object with a name.
We just need to strip the name in R when calling addHolwerTrack_id
.
Currently the call is session$sendCustomMessage(message_name, list(track = track, track_name = track_name, play = play_track))
where track
is a named string.
Simply adding unname
(and as.list
for good measure) fixed the problem:
addTrack <- function(id, track, play_track = FALSE, session = getDefaultReactiveDomain()) {
if (is.null(names(track))) {
track_name <- sub("\\.[^\\.]+$", "", basename(track[1]))
} else {
track_name <- names(track)
}
message_name <- paste0("addHowlerTrack_", session$ns(id))
track_info <- list(track = as.list(unname(track)),
track_name = as.list(track_name),
play = play_track)
session$sendCustomMessage(message_name, track_info)
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.