summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml10
-rw-r--r--ChangeLog7
-rw-r--r--INSTALL4
-rw-r--r--README.md23
-rw-r--r--README.rst220
-rw-r--r--src/config.h2
-rw-r--r--src/libpiano/response.c27
-rw-r--r--src/player.c46
-rw-r--r--src/player.h1
-rw-r--r--src/ui.c107
-rw-r--r--src/ui_act.c28
11 files changed, 386 insertions, 89 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9b61ea6..536a7d9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,12 +9,14 @@ on:
jobs:
build:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - name: deps
- run: sudo apt install libao-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libcurl4-gnutls-dev libgcrypt20-dev libjson-c-dev libpth-dev pkg-config build-essential
- - name: make
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt install libao-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libcurl4-gnutls-dev libgcrypt20-dev libjson-c-dev libpth-dev pkg-config build-essential
+ - name: Build pianobar
run: make
diff --git a/ChangeLog b/ChangeLog
index 50b78e1..d36525a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Release 2022.04.01
+
+- Not a joke
+- Fix compilation with ffmpeg 5.0 and replace deprecated function
+- Improved retry handling with unreliable HTTP proxies
+- Minor UI improvements
+
Release 2020.11.28
- Support changing station modes
diff --git a/INSTALL b/INSTALL
index 2d36705..fa18296 100644
--- a/INSTALL
+++ b/INSTALL
@@ -7,10 +7,10 @@ Dependencies
- gmake
- pthreads
- libao
-- libcurl
+- libcurl>=7.32.0
- gcrypt[1]
- json-c
-- ffmpeg>=3.3 [2]
+- ffmpeg>=5.1 [2]
- UTF-8 console/locale
[1] with blowfish cipher enabled
diff --git a/README.md b/README.md
deleted file mode 100644
index 971228e..0000000
--- a/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# pianobar
-
-![build](https://github.com/PromyLOPh/pianobar/workflows/build/badge.svg)
-
-pianobar is a console client for the personalized web radio [Pandora](https://www.pandora.com).
-
-### Features
-
-* Play and manage (create, add more music, delete, rename, ...) your stations.
-* Rate played songs and let pandora explain why they have been selected.
-* Show upcoming songs/song history.
-* Configure keybindings.
-* last.fm scrobbling support (external application)
-* Proxy support for listeners outside the USA.
-
-### Source Code
-
-The source code can be downloaded at [github.com](https://github.com/PromyLOPh/pianobar)
-or [6xq.net](https://6xq.net/pianobar/).
-
-### Download/Installation
-
-There are community provided packages available for most Linux distributions (see your distribution’s package manager), macOS ([MacPorts](https://www.macports.org) or [Homebrew](https://brew.sh)) and *BSD as well as a [native Windows port](https://github.com/thedmd/pianobar-windows).
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..c4d812e
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,220 @@
+pianobar
+========
+
+pianobar is a free/open-source, console-based client for the personalized
+online radio Pandora_.
+
+.. _Pandora: http://www.pandora.com
+
+.. image:: https://6xq.net/pianobar/pianobar-screenshot.png
+ :target: https://6xq.net/pianobar/pianobar-screenshot.png
+ :alt: pianobar screenshot
+
+Features
+--------
+
+- play and manage (create, add more music, delete, rename, ...) stations
+- rate songs and explain why they have been selected
+- upcoming songs/song history
+- customize keybindings and text output (see `configuration example`_)
+- remote control and eventcmd interface (send tracks to last.fm_, for example)
+- proxy support for listeners outside the USA
+
+.. _last.fm: https://www.last.fm
+.. _configuration example: https://github.com/PromyLOPh/pianobar/blob/master/contrib/config-example
+
+Download
+--------
+
+There are community provided packages available for most Linux distributions
+(see your distribution’s package manager), Mac OS X (via homebrew_)
+and \*BSD as well as a `native Windows port`_.
+
+.. _homebrew: http://brew.sh/
+.. _native Windows Port: https://github.com/thedmd/pianobar-windows
+
+The current pianobar release is 2022.04.01_ (sha256__, sign__). More recent and
+experimental code is available at GitHub_ and the local gitweb_. Older releases
+are available here:
+
+- 2020.11.28_ (sha256__, sign__)
+- 2020.04.05_ (sha256__, sign__)
+- 2019.02.14_ (sha256__, sign__)
+- 2019.01.25_ (sha256__, sign__)
+- 2018.06.22_ (sha256__, sign__)
+- 2017.08.30_ (sha256__, sign__)
+- 2016.06.02_ (sha256__, sign__)
+- 2015.11.22_ (sha256__, sign__)
+- 2014.09.28_ (sha256__, sign__)
+- 2014.06.08_ (sha256__, sign__)
+- 2013.09.15_ (sha256__, sign__)
+- 2013.05.19_ (sha256__, sign__)
+- 2012.12.01_ (sha256__, sign__)
+- 2012.09.07_ (sha256__, sign__)
+- 2012.06.24_ (sha256__, sign__)
+- 2012.05.06_ (sha256__, sign__)
+- 2012.04.24_ (sha256__, sign__)
+- 2012.01.10_ (sha256__, sign__)
+- 2011.12.11_ (sha256__, sign__)
+- 2011.11.11_ (sha256__, sign__)
+- 2011.11.09_ (sha256__, sign__)
+- 2011.09.22_ (sha256__, sign__)
+- 2011.07.09_ (sha256__, sign__)
+- 2011.04.27_ (sha256__, sign__)
+- 2011.04.10_ (sha256__, sign__)
+- 2011.01.24_ (sha256__)
+- 2010.11.06_ (sha1__)
+- 2010.10.07_ (sha1__)
+- 2010.08.21_ (sha1__)
+
+.. _2022.04.01: https://6xq.net/pianobar/pianobar-2022.04.01.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2022.04.01.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2022.04.01.tar.bz2.asc
+.. _2020.11.28: https://6xq.net/pianobar/pianobar-2020.11.28.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2020.11.28.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2020.11.28.tar.bz2.asc
+.. _snapshot: http://github.com/PromyLOPh/pianobar/tarball/master
+.. _GitHub: http://github.com/PromyLOPh/pianobar/
+.. _gitweb: https://6xq.net/git/lars/pianobar.git/
+.. _2020.04.05: https://6xq.net/pianobar/pianobar-2020.04.05.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2020.04.05.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2020.04.05.tar.bz2.asc
+.. _2019.02.14: https://6xq.net/pianobar/pianobar-2019.02.14.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2019.02.14.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2019.02.14.tar.bz2.asc
+.. _2019.01.25: https://6xq.net/pianobar/pianobar-2019.01.25.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2019.01.25.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2019.01.25.tar.bz2.asc
+.. _2018.06.22: https://6xq.net/pianobar/pianobar-2018.06.22.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2018.06.22.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2018.06.22.tar.bz2.asc
+.. _2017.08.30: https://6xq.net/pianobar/pianobar-2017.08.30.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2017.08.30.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2017.08.30.tar.bz2.asc
+.. _2016.06.02: https://6xq.net/pianobar/pianobar-2016.06.02.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2016.06.02.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2016.06.02.tar.bz2.asc
+.. _2015.11.22: https://6xq.net/pianobar/pianobar-2015.11.22.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2015.11.22.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2015.11.22.tar.bz2.asc
+.. _2014.09.28: https://6xq.net/pianobar/pianobar-2014.09.28.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2014.09.28.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2014.09.28.tar.bz2.asc
+.. _2014.06.08: https://6xq.net/pianobar/pianobar-2014.06.08.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2014.06.08.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2014.06.08.tar.bz2.asc
+.. _2013.09.15: https://6xq.net/pianobar/pianobar-2013.09.15.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2013.09.15.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2013.09.15.tar.bz2.asc
+.. _2013.05.19: https://6xq.net/pianobar/pianobar-2013.05.19.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2013.05.19.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2013.05.19.tar.bz2.asc
+.. _2012.12.01: https://6xq.net/pianobar/pianobar-2012.12.01.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2012.12.01.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2012.12.01.tar.bz2.asc
+.. _2012.09.07: https://6xq.net/pianobar/pianobar-2012.09.07.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2012.09.07.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2012.09.07.tar.bz2.asc
+.. _2012.06.24: https://6xq.net/pianobar/pianobar-2012.06.24.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2012.06.24.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2012.06.24.tar.bz2.asc
+.. _2012.05.06: https://6xq.net/pianobar/pianobar-2012.05.06.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2012.05.06.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2012.05.06.tar.bz2.asc
+.. _2012.04.24: https://6xq.net/pianobar/pianobar-2012.04.24.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2012.04.24.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2012.04.24.tar.bz2.asc
+.. _2012.01.10: https://6xq.net/pianobar/pianobar-2012.01.10.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2012.01.10.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2012.01.10.tar.bz2.asc
+.. _2011.12.11: https://6xq.net/pianobar/pianobar-2011.12.11.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.12.11.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.12.11.tar.bz2.asc
+.. _2011.11.11: https://6xq.net/pianobar/pianobar-2011.11.11.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.11.11.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.11.11.tar.bz2.asc
+.. _2011.11.09: https://6xq.net/pianobar/pianobar-2011.11.09.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.11.09.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.11.09.tar.bz2.asc
+.. _2011.09.22: https://6xq.net/pianobar/pianobar-2011.09.22.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.09.22.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.09.22.tar.bz2.asc
+.. _2011.07.09: https://6xq.net/pianobar/pianobar-2011.07.09.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.07.09.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.07.09.tar.bz2.asc
+.. _2011.04.27: https://6xq.net/pianobar/pianobar-2011.04.27.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.04.27.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.04.27.tar.bz2.asc
+.. _2011.04.10: https://6xq.net/pianobar/pianobar-2011.04.10.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.04.10.tar.bz2.sha256
+__ https://6xq.net/pianobar/pianobar-2011.04.10.tar.bz2.asc
+.. _2011.01.24: https://6xq.net/pianobar/pianobar-2011.01.24.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2011.01.24.tar.bz2.sha256
+.. _2010.11.06: https://6xq.net/pianobar/pianobar-2010.11.06.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2010.11.06.tar.bz2.sha1
+.. _2010.10.07: https://6xq.net/pianobar/pianobar-2010.10.07.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2010.10.07.tar.bz2.sha1
+.. _2010.08.21: https://6xq.net/pianobar/pianobar-2010.08.21.tar.bz2
+__ https://6xq.net/pianobar/pianobar-2010.08.21.tar.bz2.sha1
+
+FAQ
+---
+
+The audio output does not work as expected. What can I do?
+ pianobar uses libao and most problems are related to a broken libao
+ configuration. Have a look at issue `#167`_ for example.
+Can I donate money? Do you have a Flattr/Bitcoin/… account?
+ No, money is not necessary to continue working on pianobar. There are many
+ other ways to support pianobar: Reporting bugs, creating `cool stuff`_
+ based on pianobar, blogging about it and the most important one: Keeping
+ Pandora alive.
+
+.. _#167: https://github.com/PromyLOPh/pianobar/issues/167
+.. _cool stuff: `addons`_
+
+External projects
+-----------------
+
+Addons
+++++++
+
+control-pianobar_
+ Scripts that interact with pianobar entirely through notification bubbles
+ and hotkeys
+pianobar.el_
+ Emacs interface for pianobar
+`pianobar-mediaplayer2`_
+ Control pianobar like any other media player through DBUS/MPRIS.
+PianobarNowPlayable_
+ Integrate pianobar with the Now Playing feature of macOS
+
+.. _control-pianobar: http://malabarba.github.io/control-pianobar/
+.. _pianobar.el: https://github.com/agrif/pianobar.el
+.. _pianobar-mediaplayer2: https://github.com/ryanswilson59/pianobar-mediaplayer2
+.. _PianobarNowPlayable: https://github.com/iDom818/PianobarNowPlayable
+
+Clients
++++++++
+
+pithos_
+ Python/GTK desktop client
+pianod_
+ Pandora UNIX daemon, based on pianobar
+Hermes_
+ Pandora Client for OS X
+
+.. _pithos: http://pithos.github.io/
+.. _pianod: http://deviousfish.com/pianod/
+.. _Hermes: http://hermesapp.org/
+
+Standalone devices
+++++++++++++++++++
+
+PandoraBar_
+ Beagleboard-based radio device running pianobar
+`Pandora’s Box`_
+ Raspberry Pi-based standalone devices running pianobar
+
+.. _PandoraBar: https://hackaday.com/2012/09/20/how-to-build-your-own-dedicated-pandora-radio/
+.. _Pandora’s Box: http://www.instructables.com/id/Pandoras-Box-An-Internet-Radio-player-made-with/
+
diff --git a/src/config.h b/src/config.h
index ea8a6ec..f3d3d3e 100644
--- a/src/config.h
+++ b/src/config.h
@@ -3,7 +3,7 @@
/* package name */
#define PACKAGE "pianobar"
-#define VERSION "2020.11.28-dev"
+#define VERSION "2022.04.01-dev"
/* glibc feature test macros, define _before_ including other files */
#define _POSIX_C_SOURCE 200809L
diff --git a/src/libpiano/response.c b/src/libpiano/response.c
index 1e79261..0be8872 100644
--- a/src/libpiano/response.c
+++ b/src/libpiano/response.c
@@ -206,7 +206,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
break;
}
- for (int i = 0; i < json_object_array_length (stations); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (stations); i++) {
PianoStation_t *tmpStation;
json_object *s = json_object_array_get_idx (stations, i);
@@ -229,7 +229,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
if (mix != NULL) {
PianoStation_t *curStation = ph->stations;
PianoListForeachP (curStation) {
- for (int i = 0; i < json_object_array_length (mix); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (mix); i++) {
json_object *id = json_object_array_get_idx (mix, i);
if (strcmp (json_object_get_string (id),
curStation->id) == 0) {
@@ -256,7 +256,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
}
assert (items != NULL);
- for (int i = 0; i < json_object_array_length (items); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (items); i++) {
json_object *s = json_object_array_get_idx (items, i);
PianoSong_t *song;
@@ -377,7 +377,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
/* get artists */
json_object *artists;
if (json_object_object_get_ex (result, "artists", &artists)) {
- for (int i = 0; i < json_object_array_length (artists); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (artists); i++) {
json_object *a = json_object_array_get_idx (artists, i);
PianoArtist_t *artist;
@@ -396,7 +396,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
/* get songs */
json_object *songs;
if (json_object_object_get_ex (result, "songs", &songs)) {
- for (int i = 0; i < json_object_array_length (songs); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (songs); i++) {
json_object *s = json_object_array_get_idx (songs, i);
PianoSong_t *song;
@@ -456,7 +456,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
/* get genre stations */
json_object *categories;
if (json_object_object_get_ex (result, "categories", &categories)) {
- for (int i = 0; i < json_object_array_length (categories); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (categories); i++) {
json_object *c = json_object_array_get_idx (categories, i);
PianoGenreCategory_t *tmpGenreCategory;
@@ -471,7 +471,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
/* get genre subnodes */
json_object *stations;
if (json_object_object_get_ex (c, "stations", &stations)) {
- for (int k = 0;
+ for (unsigned int k = 0;
k < json_object_array_length (stations); k++) {
json_object *s =
json_object_array_get_idx (stations, k);
@@ -520,12 +520,13 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
assert (reqData != NULL);
json_object *explanations;
- if (json_object_object_get_ex (result, "explanations", &explanations)) {
+ if (json_object_object_get_ex (result, "explanations", &explanations) &&
+ json_object_array_length (explanations) > 0) {
reqData->retExplain = malloc (strSize *
sizeof (*reqData->retExplain));
strncpy (reqData->retExplain, "We're playing this track "
"because it features ", strSize);
- for (int i = 0; i < json_object_array_length (explanations); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (explanations); i++) {
json_object *e = json_object_array_get_idx (explanations,
i);
json_object *f;
@@ -573,7 +574,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
/* songs */
json_object *songs;
if (json_object_object_get_ex (music, "songs", &songs)) {
- for (int i = 0; i < json_object_array_length (songs); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (songs); i++) {
json_object *s = json_object_array_get_idx (songs, i);
PianoSong_t *seedSong;
@@ -594,7 +595,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
/* artists */
json_object *artists;
if (json_object_object_get_ex (music, "artists", &artists)) {
- for (int i = 0; i < json_object_array_length (artists); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (artists); i++) {
json_object *a = json_object_array_get_idx (artists, i);
PianoArtist_t *seedArtist;
@@ -622,7 +623,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
continue;
}
assert (json_object_is_type (val, json_type_array));
- for (int i = 0; i < json_object_array_length (val); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (val); i++) {
json_object *s = json_object_array_get_idx (val, i);
PianoSong_t *feedbackSong;
@@ -665,7 +666,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
json_object *availableModes;
if (json_object_object_get_ex (result, "availableModes", &availableModes)) {
- for (int i = 0; i < json_object_array_length (availableModes); i++) {
+ for (unsigned int i = 0; i < json_object_array_length (availableModes); i++) {
json_object *val = json_object_array_get_idx (availableModes, i);
assert (json_object_is_type (val, json_type_object));
diff --git a/src/player.c b/src/player.c
index b8eeead..753d490 100644
--- a/src/player.c
+++ b/src/player.c
@@ -235,7 +235,7 @@ static bool openStream (player_t * const player) {
softfail ("avcodec_parameters_to_context");
}
- AVCodec * const decoder = avcodec_find_decoder (cp->codec_id);
+ const AVCodec * const decoder = avcodec_find_decoder (cp->codec_id);
if (decoder == NULL) {
softfail ("find_decoder");
}
@@ -282,11 +282,13 @@ static bool openFilter (player_t * const player) {
/* abuffer */
AVRational time_base = player->st->time_base;
+ char channelLayout[128];
+ av_channel_layout_describe(&player->cctx->ch_layout, channelLayout, sizeof(channelLayout));
snprintf (strbuf, sizeof (strbuf),
- "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64,
+ "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s",
time_base.num, time_base.den, cp->sample_rate,
av_get_sample_fmt_name (player->cctx->sample_fmt),
- cp->channel_layout);
+ channelLayout);
if ((ret = avfilter_graph_create_filter (&player->fabuf,
avfilter_get_by_name ("abuffer"), "source", strbuf, NULL,
player->fgraph)) < 0) {
@@ -340,7 +342,7 @@ static bool openDevice (player_t * const player) {
memset (&aoFmt, 0, sizeof (aoFmt));
aoFmt.bits = av_get_bytes_per_sample (avformat) * 8;
assert (aoFmt.bits > 0);
- aoFmt.channels = cp->channels;
+ aoFmt.channels = cp->ch_layout.nb_channels;
aoFmt.rate = getSampleRate (player);
aoFmt.byte_format = AO_FMT_NATIVE;
@@ -401,12 +403,12 @@ BarPlayerMode BarPlayerGetMode (player_t * const player) {
static int play (player_t * const player) {
assert (player != NULL);
const int64_t minBufferHealth = player->settings->bufferSecs;
-
- AVPacket pkt;
AVCodecContext * const cctx = player->cctx;
- av_init_packet (&pkt);
- pkt.data = NULL;
- pkt.size = 0;
+
+ AVPacket *pkt = av_packet_alloc ();
+ assert (pkt != NULL);
+ pkt->data = NULL;
+ pkt->size = 0;
AVFrame *frame = NULL;
frame = av_frame_alloc ();
@@ -418,21 +420,25 @@ static int play (player_t * const player) {
const double timeBase = av_q2d (player->st->time_base);
while (!shouldQuit (player) && drainMode != DONE) {
if (drainMode == FILL) {
- ret = av_read_frame (player->fctx, &pkt);
+ ret = av_read_frame (player->fctx, pkt);
if (ret == AVERROR_EOF) {
/* enter drain mode */
drainMode = DRAIN;
avcodec_send_packet (cctx, NULL);
debugPrint (DEBUG_AUDIO, "decoder entering drain mode after EOF\n");
- } else if (pkt.stream_index != player->streamIdx) {
+ } else if (pkt->stream_index != player->streamIdx) {
/* unused packet */
- av_packet_unref (&pkt);
+ av_packet_unref (pkt);
continue;
} else if (ret < 0) {
/* error, abort */
/* mark the EOF, so that BarAoPlayThread can quit*/
- debugPrint (DEBUG_AUDIO, "av_read_frame failed with code %i, sending "
- "NULL frame\n", ret);
+ char error[AV_ERROR_MAX_STRING_SIZE];
+ if (av_strerror(ret, error, sizeof(error)) < 0) {
+ strncpy (error, "(unknown)", sizeof(error)-1);
+ }
+ debugPrint (DEBUG_AUDIO, "av_read_frame failed with code %i (%s), "
+ "sending NULL frame\n", ret, error);
pthread_mutex_lock (&player->aoplayLock);
const int rt = av_buffersrc_add_frame (player->fabuf, NULL);
assert (rt == 0);
@@ -441,7 +447,7 @@ static int play (player_t * const player) {
break;
} else {
/* fill buffer */
- avcodec_send_packet (cctx, &pkt);
+ avcodec_send_packet (cctx, pkt);
}
}
@@ -490,9 +496,10 @@ static int play (player_t * const player) {
} while (bufferHealth > minBufferHealth);
}
- av_packet_unref (&pkt);
+ av_packet_unref (pkt);
}
av_frame_free (&frame);
+ av_packet_free (&pkt);
debugPrint (DEBUG_AUDIO, "decoder is done, waiting for ao player\n");
pthread_join (aoplaythread, NULL);
@@ -532,7 +539,9 @@ void *BarPlayerThread (void *data) {
if (openFilter (player) && openDevice (player)) {
changeMode (player, PLAYER_PLAYING);
BarPlayerSetVolume (player);
- retry = play (player) == AVERROR_INVALIDDATA &&
+ const int ret = play (player);
+ retry = (ret == AVERROR_INVALIDDATA ||
+ ret == -ECONNRESET) &&
!player->interrupted;
} else {
/* filter missing or audio device busy */
@@ -582,8 +591,7 @@ void *BarAoPlayThread (void *data) {
}
pthread_mutex_unlock (&player->aoplayLock);
- const int numChannels = av_get_channel_layout_nb_channels (
- filteredFrame->channel_layout);
+ const int numChannels = filteredFrame->ch_layout.nb_channels;
const int bps = av_get_bytes_per_sample (filteredFrame->format);
ao_play (player->aoDev, (char *) filteredFrame->data[0],
filteredFrame->nb_samples * numChannels * bps);
diff --git a/src/player.h b/src/player.h
index 3179785..2e44aed 100644
--- a/src/player.h
+++ b/src/player.h
@@ -34,6 +34,7 @@ THE SOFTWARE.
#include <ao/ao.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
+#include <libavcodec/avcodec.h>
#include <piano.h>
#include "settings.h"
diff --git a/src/ui.c b/src/ui.c
index 1ab54ae..0c7386a 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -161,8 +161,8 @@ static size_t httpFetchCb (char *ptr, size_t size, size_t nmemb,
/* libcurl progress callback. aborts the current request if user pressed ^C
*/
-int progressCb (void * const data, double dltotal, double dlnow,
- double ultotal, double ulnow) {
+int progressCb (void * const data, curl_off_t dltotal, curl_off_t dlnow,
+ curl_off_t ultotal, curl_off_t ulnow) {
const sig_atomic_t lint = *((sig_atomic_t *) data);
if (lint) {
return 1;
@@ -171,6 +171,27 @@ int progressCb (void * const data, double dltotal, double dlnow,
}
}
+/* Error codes from libcurl, which may be temporary and should be retried.
+ */
+static bool temporaryCurlError (const CURLcode code) {
+ switch (code) {
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ case CURLE_WEIRD_SERVER_REPLY:
+ case CURLE_READ_ERROR:
+ case CURLE_OPERATION_TIMEDOUT:
+ case CURLE_SSL_CONNECT_ERROR:
+ case CURLE_GOT_NOTHING:
+ case CURLE_SEND_ERROR:
+ case CURLE_RECV_ERROR:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
#define setAndCheck(k,v) \
httpret = curl_easy_setopt (http, k, v); \
assert (httpret == CURLE_OK);
@@ -203,8 +224,8 @@ static CURLcode BarPianoHttpRequest (CURL * const http,
setAndCheck (CURLOPT_POSTFIELDS, req->postData);
setAndCheck (CURLOPT_WRITEFUNCTION, httpFetchCb);
setAndCheck (CURLOPT_WRITEDATA, &buffer);
- setAndCheck (CURLOPT_PROGRESSFUNCTION, progressCb);
- setAndCheck (CURLOPT_PROGRESSDATA, &lint);
+ setAndCheck (CURLOPT_XFERINFOFUNCTION, progressCb);
+ setAndCheck (CURLOPT_XFERINFODATA, &lint);
setAndCheck (CURLOPT_NOPROGRESS, 0);
setAndCheck (CURLOPT_POST, 1);
setAndCheck (CURLOPT_TIMEOUT, settings->timeout);
@@ -248,7 +269,7 @@ static CURLcode BarPianoHttpRequest (CURL * const http,
do {
httpret = curl_easy_perform (http);
++retry;
- if (httpret == CURLE_OPERATION_TIMEDOUT) {
+ if (temporaryCurlError (httpret)) {
free (buffer.data);
buffer.data = NULL;
buffer.pos = 0;
@@ -823,6 +844,49 @@ size_t BarUiListSongs (const BarApp_t * const app,
return i;
}
+enum {
+ NO_DURATION = 0,
+};
+#define NO_POSTFIX ""
+
+/* Print song information to the eventcmd stream
+ * @param Event command stream.
+ * @param Song information.
+ * @param Printed key name postfix, use NO_POSTFIX to print bare keys.
+ * @param Override song length from song parameter, use NO_DURATION if unavailable.
+ */
+static void BarUiEventcmdPrintSong (FILE * restrict stream,
+ const PianoSong_t * const song, const char * const postfix,
+ const unsigned int songDuration) {
+ assert (song != NULL);
+ assert (stream != NULL);
+ assert (postfix != NULL);
+
+ fprintf (stream,
+ "artist%s=%s\n"
+ "title%s=%s\n"
+ "album%s=%s\n"
+ "coverArt%s=%s\n"
+ "rating%s=%i\n"
+ "detailUrl%s=%s\n"
+ "songDuration%s=%u\n",
+ postfix,
+ song->artist,
+ postfix,
+ song->title,
+ postfix,
+ song->album,
+ postfix,
+ song->coverArt,
+ postfix,
+ song->rating,
+ postfix,
+ song->detailUrl,
+ postfix,
+ songDuration == NO_DURATION ? song->length : songDuration
+ );
+}
+
/* Excute external event handler
* @param settings containing the cmdline
* @param event type
@@ -879,36 +943,37 @@ void BarUiStartEventCmd (const BarSettings_t *settings, const char *type,
pthread_mutex_unlock (&player->lock);
fprintf (pipeWriteFd,
- "artist=%s\n"
- "title=%s\n"
- "album=%s\n"
- "coverArt=%s\n"
"stationName=%s\n"
"songStationName=%s\n"
"pRet=%i\n"
"pRetStr=%s\n"
"wRet=%i\n"
"wRetStr=%s\n"
- "songDuration=%u\n"
- "songPlayed=%u\n"
- "rating=%i\n"
- "detailUrl=%s\n",
- curSong == NULL ? "" : curSong->artist,
- curSong == NULL ? "" : curSong->title,
- curSong == NULL ? "" : curSong->album,
- curSong == NULL ? "" : curSong->coverArt,
+ "songPlayed=%u\n",
curStation == NULL ? "" : curStation->name,
songStation == NULL ? "" : songStation->name,
pRet,
PianoErrorToStr (pRet),
wRet,
curl_easy_strerror (wRet),
- songDuration,
- songPlayed,
- curSong == NULL ? PIANO_RATE_NONE : curSong->rating,
- curSong == NULL ? "" : curSong->detailUrl
+ songPlayed
);
+ if (curSong != NULL) {
+ BarUiEventcmdPrintSong (pipeWriteFd, curSong, NO_POSTFIX, songDuration);
+ }
+
+ const PianoSong_t *nextSong = PianoListNextP (curSong);
+ if (nextSong != NULL) {
+ unsigned int i = 0;
+ PianoListForeachP (nextSong) {
+ char postfix[16];
+ snprintf (postfix, sizeof(postfix)-1, "Next%i", i);
+ BarUiEventcmdPrintSong (pipeWriteFd, nextSong, postfix, NO_DURATION);
+ i++;
+ }
+ }
+
if (stations != NULL) {
/* send station list */
PianoStation_t **sortedStations = NULL;
diff --git a/src/ui_act.c b/src/ui_act.c
index 43d5b6e..fa5c43b 100644
--- a/src/ui_act.c
+++ b/src/ui_act.c
@@ -271,8 +271,12 @@ BarUiActCallback(BarUiActExplain) {
BarUiMsg (&app->settings, MSG_INFO, "Receiving explanation... ");
if (BarUiActDefaultPianoCall (PIANO_REQUEST_EXPLAIN, &reqData)) {
- BarUiMsg (&app->settings, MSG_INFO, "%s\n", reqData.retExplain);
- free (reqData.retExplain);
+ if (reqData.retExplain == NULL) {
+ BarUiMsg (&app->settings, MSG_ERR, "No explanation provided.\n");
+ } else {
+ BarUiMsg (&app->settings, MSG_INFO, "%s\n", reqData.retExplain);
+ free (reqData.retExplain);
+ }
}
BarUiActDefaultEventcmd ("songexplain");
}
@@ -785,7 +789,12 @@ BarUiActCallback(BarUiActManageStation) {
}
/* enable submenus depending on data availability */
- strcpy (question, "Delete ");
+ if (reqData.info.artistSeeds != NULL ||
+ reqData.info.songSeeds != NULL ||
+ reqData.info.stationSeeds != NULL ||
+ reqData.info.feedback != NULL) {
+ strcpy (question, "Delete ");
+ }
if (reqData.info.artistSeeds != NULL) {
strcat (question, "[a]rtist");
*allowedPos++ = 'a';
@@ -814,15 +823,22 @@ BarUiActCallback(BarUiActManageStation) {
strcat (question, "[f]eedback");
*allowedPos++ = 'f';
}
- /* station mode is always available */
if (allowedPos != allowedActions) {
strcat (question, "? ");
}
- strcat (question, "Manage [m]ode? ");
- *allowedPos++ = 'm';
+ /* station mode is not available for QuickMix. */
+ if (!selStation->isQuickMix) {
+ strcat (question, "Manage [m]ode? ");
+ *allowedPos++ = 'm';
+ }
*allowedPos = '\0';
+ if (allowedPos == allowedActions) {
+ BarUiMsg (&app->settings, MSG_INFO, "No actions available.\n");
+ return;
+ }
+
assert (strlen (question) < sizeof (question) / sizeof (*question));
BarUiMsg (&app->settings, MSG_QUESTION, "%s", question);