Spotify 客户端今年推出了「远程群组点歌房」功能,类似虾米做过的「与好友一起听」和「趴间」,但分享点歌房链接的体验做得还不是特别顺畅,好友还需要有平台账号,链接也存在时效。 现阶段相比之下,视频直播的产品体验更加完整,例如小破站就不存在这类问题。所以作为用户,我是不是也可以以直播推流的形式完成「同步听 Spotify 音乐」的效果?
📎 桌面方案
在桌面端,使用 Spotify 客户端播放音乐,使用 OBS 采集声卡输出,并推流至 RTMP 服务器即可。 在 MacOS 中,还可以通过 BlackHole 2ch 提供虚拟声卡,配合系统自带的「音频 MIDI 设置 - 多输出设备」实现一份音频同时输出至虚拟和物理声卡。
进一步,如果还想根据不同程序做更细粒度的输出管理,可以了解 Audio Hijack,这里则不过多介绍。
📎 无头方案
📎 播放 - librespot
社区中比较热门的 spotifyd 可以以 Headless 的形式使用 Spotify,但其实我们只需要其下层的 librespot 即可完成所需的 登录平台 以及 播放音乐 的能力。 librespot 能够以 Spotify Connect 的形式呈现为一台播放设备,由手机或桌面的官方客户端远程控制。
例如在 Shell 中:
cargo install librespot
librespot -u {USERNAME} -p {PASSWORD} -n "My Musicbox" --backend pipe
📎 声卡 - PulseAudio
librespot 中有多种输出方式 (即 backend 参数)。
上面示例中的管道 (pipe) 输出较为特殊,它会输出文件原始数据,接受方需要以一定速率、有节奏地的读取,否则该数据流会过快结束,librespot 收到结束信号后即开始前往下一首歌曲。例如当 stdout 为 fifo /dev/null
,由于文件输出快速完成,因此最终会表现为播放一首歌曲两三秒后,librespot 即跳至下一首,并不断重复该过程。
使用其它输出方式则没有这一问题,这里我使用 PulseAudio:
# 安装
sudo apt install pulseaudio pulseaudio-utils
# 允许当前用户使用
sudo usermod -aG pulse,pulse-access $(whoami)
# 启动
pulseaudio -D
librespot 默认版本只支持了个别 backend,因此还需要 单独编译 一个支持 PulseAudio 的版本:
git clone https://github.com/librespot-org/librespot.git
cd librespot
cargo build --release --no-default-features --features pulseaudio-backend
然后准备一张虚拟声卡:
# 删除默认声卡
pactl unload-module 0
# 添加虚拟声卡
pactl load-module module-null-sink sink_name=SpotSink
便可以将音乐输出至 PulseAudio:
./target/release/librespot -u {USERNAME} -p {PASSWORD} -n {DEVICE_NAME} --backend pulseaudio
📎 推流 - FFmpeg
使用 NodeMediaServer 作为测试 RTMP 服务器:
docker run --name nms -d -p 1935:1935 -p 8001:8000 illuspas/node-media-server
使用 FFmpeg 将音频推流:
# 安装 FFmpeg
sudo apt install ffmpeg
# 采集并推流
ffmpeg -f pulse -i "SpotSink.monitor" -f flv rtmp://127.0.0.1/live/spotbox
再加入背景图片作为视频画面:
# 采集并推流
ffmpeg \
-loop 1 -r 15 -f image2 -s 1280x720 -i ./background.jpg \
-f pulse -i "SpotSink.monitor" \
-c:a mp3 -c:v libx264 -preset ultrafast -vf "scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720,fps=30,format=yuv420p" \
-f flv \
rtmp://127.0.0.1/live/spotbox
打开 http://127.0.0.1:8001/admin/ 查看 NodeMediaServer 测试流:
📎 容器化
最后,将整个过程容器化:
# Dockerfile
FROM ubuntu:20.04
# Setup APT Dependencies
RUN apt-get update -y
RUN DEBIAN_FRONTEND=nointeractive apt-get upgrade -y
RUN DEBIAN_FRONTEND=nointeractive apt-get install -y \
curl git \
build-essential libasound2-dev pkg-config \
pulseaudio pulseaudio-utils \
ffmpeg
WORKDIR /app
# Setup Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh
RUN chmod +x rustup.sh
RUN ./rustup.sh -y
# Setup librespot
RUN git clone https://github.com/librespot-org/librespot.git
RUN cd librespot && \
git checkout v0.3.1 && \
~/.cargo/bin/cargo build --release --no-default-features --features pulseaudio-backend
# Setup Script
COPY ./entrypoint.sh .
# Start
CMD ./entrypoint.sh
# entrypoint.sh
## Setup PulseAudio
rm -rf /var/run/pulse /var/lib/pulse /root/.config/pulse
usermod -aG pulse,pulse-access root
pulseaudio -D --system
pactl unload-module 0
pactl load-module module-null-sink sink_name=SpotSink
## Setup Background Image
curl -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36" "$BG_IMG_URI" > background.jpg
## librespot
./librespot/target/release/librespot \
-u "$USERNAME" -p "$PASSWORD" -n "$DEVICE_NAME" \
--backend pulseaudio | head -c 1G > librespot.log 2>&1 < /dev/null &
## ffmpeg
ffmpeg \
-loop 1 -r 15 -f image2 -s 1280x720 -i ./background.jpg \
-f pulse -i "SpotSink.monitor" \
-c:a aac -c:v libx264 -preset ultrafast -vf "scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720,fps=30,format=yuv420p" \
-f flv \
"$OUTPUT" | head -c 1G > ffmpeg.log 2>&1 < /dev/null &
## log
tail -f ./librespot.log ./ffmpeg.log
编译:
docker build . -t spotbox
运行:
docker run -d -e USERNAME="..." -e PASSWORD="..." -e DEVICE_NAME="My Spotbox" -e BG_IMG_URI="https://images.unsplash.com/photo-1608634769432-f9b6524aa2bf?fit=crop&fm=jpg&h=720&q=80&w=1280" -e OUTPUT="rtmp://172.17.0.2/live/spotbox" --name spotbox spotbox
📎 参考
- Spotify
- Spotify Connect
- Spotify for Linux
- spotifyd
- librespot
- librespot - Audio Backends
- librespot - Compiling
- librespot - Tracks skip after few seconds when piping passthrough audio #521
- PulseAudio
- OBS
- BlackHole 2ch
- Audio Hijack
- NodeMediaServer
- https://github.com/Spotifyd/spotifyd/issues/354
- https://askubuntu.com/questions/28176/how-do-i-run-pulseaudio-in-a-headless-server-installation#fromHistory
- https://unix.stackexchange.com/questions/174379/how-can-i-create-a-virtual-output-in-pulseaudio#fromHistory
- https://trac.ffmpeg.org/wiki/StreamingGuide
- https://stackoverflow.com/questions/24961127/how-to-create-a-video-from-images-with-ffmpeg
- https://superuser.com/questions/547296/resizing-videos-with-ffmpeg-avconv-to-fit-into-static-sized-player/1136305#1136305