写一个 Android 本地音乐播放器的心路历程 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
novato
V2EX    分享创造

写一个 Android 本地音乐播放器的心路历程

  •  
  •   2019-10-08 11:58:09 +08:00 2020 次点击
    这是一个创建于 2227 天前的主题,其中的信息可能已经有所发展或是发生改变。

    开始的想法很简单,就是能通过 wifi 传 mp3 到手机上播放,有单曲循环、顺序播放、目录循环什么的就行了。客户端应不需装任何软件,就用浏览器传文件。
    但市面上好像没有这种应用,不是要数据线拷文件,就是要在线播放它提供的歌曲,胡里花哨的一大堆不需要的功能,还几十、上百兆的。
    那我就着手自己写一个吧,首先要一个 android http server,请参考
    [用一个安卓 app 实验全栈开发] ,这个有了之后又要加入文件管理功能,比如:创建新文件夹、移动、删除、重命名等。把这个简单的文件管理界面加上后,再加上播放器相关的:单曲播放、单曲循环、顺序播放、目录循环。因为这是用浏览器的<audio>标签播放,所以局域网内的其它人也可以同时播放你手机上的音乐。
    那不是可以当作文件服务器用吗?干嘛限制只能上传 mp3 ?所以要让它可以上传十几个 G 的蓝光电影,或几百兆的 office 安装包。别人通过浏览器可以下载(或在线播放)你手机上的文件。好了,这些又有了之后感觉不太过瘾。
    既然整个 boost asio 都在上面,顺便也写个 socks5 代理吧,让别人可以通过你手机代理上网,如果你手机有 vpn 账号的话还是有点用的。

    目前所有的一切还在局域网内,要让它上广域网,界面是 vuejs 写的,运行在安卓 webview 里,我觉得应该把 webrtc 加上,webrtc 需要个 signaling server,然后我就用 nodejs 写了个 signaling server 做成了 docker 镜像(包含 stun/turn server ),随便在哪个有公网 ip 的服务器上 run 起来,不需要域名。这个 app 里再加上个“服务器配置界面”,可以配置多个服务器,就像 wow 的不同世界服一样,但要让它同时与不同服务器的玩家交互,就要有个 id 来标识它,就用手机硬件序列号的 hash 做 id 吧。比如玩家 a 同时在 s1,s2 两台服务器上出现的话,玩家 b 也在这两台服务器,那么 a 与 b 只会建立一个 webrtc 连接,因为同一 id 的玩家已有连接的话就不会再建立了。

    流程是这样的:玩家打开 app,就会同时建立 N 个 websocket 连接到他配置的服务器去,配置了几个就连几个,如果没有一个服务器的话,那他就完全在自己局域网内当文件服务器用,每个公网服务器会随机选最多 100 个其它玩家的 id 发给它通过加密后 websocket 连接(就像 https ),然后这个安卓 app 会把该服务器当作 signaling server,与这些玩家建立直接连接其后与这些玩家的交互就不再经过任何服务器了。所以我又加了几个界面:附近玩家列表(直连的),私聊界面,附近频道,世界频道,及个人信息配置界面:昵称、头像、签名,是否允许加好友,是否允许音频 /视频聊天,是否允许被当作代理(这个后面会解释)。
    私聊界面就类似微信的,可以发语音、文字、图片、文件什么的,还可以申请语音 /视频聊天,及请求代理。有的手机浏览器版本太低,建立实时音频 /视频聊天会失败,我个人测试用一加 5t 与华为平板可以视频聊天,但是与 nexus 6p 则会失败,可能要 Android7 以上的 webview 才行吧。不过这个不重要了,因为可以发录音文件嘛,就像微信那样按下说话,松开发送那种而且还可以在附近频道群发。如果有人发垃圾消息可以屏蔽他,屏蔽后会与他断开直连,并不再接收其世界频道消息;有些人也可以加为好友,好友列表里的玩家会被优先直连,所有玩家不需要注册,他的手机就是他的 id,除非换手机。

    到目前为止还没什么有创意的。上面说了每个玩家手机上都有个 socks5 代理,如果让别的玩家做代理服务器上网呢?
    这个问题困扰了我许久,最初想用 udp 打洞穿透,然后 tcp 走 udp,但据说有些手机网 ISP 会屏蔽 udp,而且这个内 /外网的 ip 建立连接还要像 ICE 那样处理,相当于是把 webrtc 的功能再实现一遍,看了几篇 rfc 文档,觉得这个不是短期能做的。开始是想让 C++层直接穿透直连,因为 tcp 的监听也是在 C++做的,都放在一起做感觉会简单点,那时把那个udt(不是 udp )库都已编译成安卓版了,这个是可靠传输的 udp C++库,试了下感觉它那个接口跟 asio 不太兼容,明明用 C++写的,还非得要搞成 BSD socket 那样 C 接口,就放弃了。况且即使这个能用也还要实现几个 rfc 文档。所以就整个放弃 C++层穿透了。
    webview 里的 webrtc 都已经直连了为什么不用呢?开始考虑的是 C++服务是运行在 Android Service 里,而 Webview 是运行在 Activity 里,service 可以长期运行,activity 切换到后台其中的代码执行就会被中断。所以又去查了下什么情况下 webview 里的 websocket 不会被中断。这篇文章说了个方法,试了一下,遗憾的是它需要用户手工开启特殊权限,而且允许这个特殊权限的接口在以后的 android 版本里还可能会变化。
    算了,就直接用 Activity 里的 webview 里的 webrtc 吧。我把本地 socks5 的监听端口号+100,开另一个 tcp 监听(我称作远程 socks5 代理端口),当这个端口有连接过来,C++层会通过本地 websocket 询问 webview 层“是否有附近玩家同意作为代理”,有的话则通过 webrtc 通道(直连通道)通知对端的 webview 再通过它本地的 websocket 通知它的 C++层建立与它自身 socks5 的 tcp 连接……。说起来太拗口了,图示如下: 流程图

    最终这样实现了,自己测试了一下效果还行,如果打开网站去查 ip 的话,会显示别人手机的 ip为了测这个还买了个 XX 云 1 核 /1G/1M 的服务器自己搭了个 vpn 用另一台手机连接测试。因为没什么人脉,都是自己搞。
    后面还想把 bt 下载也加进去,是用 webtorrent ( js 库)呢?还是用 libtorrent ( C++库)?我是倾向用 webtorrent,因为用 js 写起来简单些,结果一测根本就不支持老一点手机的 webview,就像那个视频聊天一样。

    算了,等以后大家换几代手机再说吧。回想当初不是只写个音乐播放器吗,先到这吧,有点扯远了。

    App 下载( 5.3M )

    默认加了一个测试服地址1 核 1G1 兆的服务器。你也可以自己起服务,然后让自己的圈内人都配置这个地址
    signaling server 的代码地址,就一百多行 nodejs 代码

    https://github.com/novice79/ss

    12 条回复    2019-10-09 22:51:06 +08:00
    duwuyan
        1
    duwuyan  
       2019-10-08 15:06:51 +08:00 via Android
    佩服
    bigbigpeng3
        2
    bigbigpeng3  
       2019-10-08 15:15:33 +08:00
    楼主很棒啊,其实我也写过类似这种的 Android 文件服务器,可以直接通过网页查看 Android 手机内的所有文件,支持直接播放。这样 Android 就是一个移动 wifi U 盘了。目前没有发现做的比较好的 App。很多都是基于 PC 的。其实总体来说有点像百度云盘,或者是文件管理器 web 版,只有局域网能访问到。
    Spoter
        3
    Spoter  
       2019-10-08 15:33:17 +08:00
    nb
    knva
        4
    knva  
       2019-10-08 16:37:08 +08:00
    所以你最后写了个 av 共享播放器?
    ciaoly
        5
    ciaoly  
       2019-10-08 16:44:45 +08:00 via Android
    楼主好优秀吖面对个性化需求,像我这种懒狗都是去找现成的 app 的。

    离线播放器有 腾讯轻听,GitHub 有 ListenerMusicPlayer ;
    WiFi 传文件(基于 HTTP 协议)有 XDA 的 Mixplorer、FastFileTransfer、chrome 应用“WebServer for chrome”;
    安卓端的 Web server 有“HTTP server powered by Apache”;
    倒是代理工具一直没找到
    hqweay
        6
    hqweay  
       2019-10-08 18:45:19 +08:00
    楼主这心理历程太真实了...等等,我最开始想干嘛来着:-D
    zhw2590582
        7
    zhw2590582  
       2019-10-09 09:01:16 +08:00
    佩服,假如 UI 再漂亮点的话就更好了
    337136897
        8
    337136897  
       2019-10-09 10:07:15 +08:00
    恕我直言,你的 UI 弄得好丑,好像 10 的 UI
    novato
        9
    novato  
    OP
       2019-10-09 17:09:41 +08:00
    @337136897 我承认,其实里面的代码也很垃圾。但总不能因为顾虑这顾虑那就不做了吧。
    你看我的名字:novato (西班牙语)== novice (英语)== 小白(中文)新手总是要起步的嘛
    hkyyx
        10
    hkyyx  
       2019-10-09 17:30:05 +08:00
    来个设计师小哥哥给楼主的设计一套 UI 啊
    novato
        11
    novato  
    OP
       2019-10-09 21:29:08 +08:00
    我倒是希望有人能测下那个远程代理的功能,因为只是我自己拿两台手机测了下,没公测过而这却是我认为这个 app 里最重要的功能。
    novato
        12
    novato  
    OP
       2019-10-09 22:51:06 +08:00
    因为 socks5 可不只是能代理浏览器,很多应用都可通过它代理。
    比如你同事在公司,你在家里,你要 ssh 到公司服务器,就可用他的手机代理过去:
    ssh -o ProxyCommand='nc -x 你手机内网 ip:57200 %h %p' 用户名 @公司服务器内网 ip
    我自己一个人没法测很多应用场景。但目前不支持 udp 代理,只简单做了 tcp 的代理。如果有需要的话以后再加上
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4992 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 09:41 PVG 17:41 LAX 01:41 JFK 04:41
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86