本系列文章是针对 https://blog.csdn.net/weixin_43668031/article/details/83962959 内容的实现所编写的。开发经历包括思考过程、重构和推翻重来。
系统:Ubuntu 18.04.2
IPFS:go-ipfs v0.4.19
Nginx:源码编译安装(添加nginx-rtmp-module)
ffmpeg:通过apt安装
监视脚本语言:Python
去年我思考了如何利用ipfs技术进行直播,构思如下图:
核心部分是流媒体传输,这里是使用HLS分片成ts小文件进行传输的,本节仅完成核心逻辑部分的实现代码。
这里要着重声明一下,Live Server 这个角色,在目前的技术背景下,这必须是个Nginx服务器,但是在未来可以和直播推流端合并在一起。就像近几年家庭入网时的光猫合并进路由器一样,“家庭网关”=光纤调制解调器+路由器+交换机+无线交换机,一台设备集成了不同的传统设备,就算在视频直播设备上面,最原始传统的采集卡只能输出原始视频未压缩的视频,随着其他模块集成,带有x264压缩模块的采集卡就诞生了,可以直接输出x264格式的视频流、文件,随着直播行业的兴起,带有推流功能的采集卡也单身了,把平台推流相关的配置导入到其中,就可以进行推流了,我相信不久就有包含上述架构的采集卡(硬件)或者打包成一体的软件突出。
在浏览器里运行ipfs节点时,我无法接受、推送来着非直连节点的信息(貌似没有转发?),也许是因为性能问题,无法处理过大广播数据,所以只能用浏览器上运行的节点直连推流的IPFS节点才能被广播到,js版本的IPFS未来应该可以做到这一点,因此在这里我用websocket来直连2个节点。
https://discuss.ipfs.io/t/pubsub-between-go-ipfs-and-js-ipfs/1744
既然这样,我就只能通过websocket来妥协一下这个问题。
次时代直播网络架构图:
在这样的网络环境下,想要直播,每个家庭?或者每个内网,都应该至少运行一个IPFS节点,和至少一个RTMP推流拉流的软件。当然我更希望的是集成在推流软件里,推流出来之后就直接是IPFS网络了。
安装软件的方式有很多,最简单的不过apt install 安装,但是在安装Nginx软件时发现apt 安装的版本没有rtmp模块,需要源码编译安装,自行加入nginx-rtmp-module模块。
#!/bin/bash
wget http://nginx.org/download/nginx-1.15.9.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/v1.2.1.tar.gz
tar -zxvf nginx-1.15.9.tar.gz
tar -zxvf v1.2.1.tar.gz
apt install gcc
cd nginx-1.15.9/
./configure --add-module=../nginx-rtmp-module-1.2.1
make
make install
在编译时可能会因为缺少依赖而失败,补全所有必要的软件包即可。
默认的安装目录在cd /usr/local/nginx/
,进入目录启动Nginx ./sbin/nginx
修改配置,进入rtmp模块vim conf/nginx.conf
加入以下内容
rtmp {
server {
listen 1935;
application live {
live on;
}
}
}
我们重新加载Nginx./sbin/nginx -s reload
这样传统的直播服务器就搭建好了,我们来测试一下、
OBS设置:设置->流->自定义->服务器rtmp://172.16.10.170/live,流密钥demo
OBS里添加点东西,开始推流
打开一个播放器potplayer,打开->打开连接->rtmp://172.16.10.170/live/demo,然后就可以通过potplayer来观看直播了
建立工作文件夹mkdir -p /root/demo /root/demo/ts
进入文件夹cd /root/demo/ts
启动ffmpeg进程 ffmpeg -nostats -re -i rtmp://172.16.10.170/live/demo -f mpegts -c copy -hls_time 10 -hls_list_size 12 -f hls live.m3u8 > ../ffmpeg.log 2>&1
其中hls_time 是每个切片的时间,hls_list_size 是一个m3u8文件包含多少个切片文件。
然后我们看一下生成的文件长什么样子:
# -*- coding: utf-8 -*-
#!/usr/bin/env python
import os
import datetime
import pyinotify
import logging
import re
import ipfsapi
base_dir=r'/root/demo'
ipfscache={}
api = ipfsapi.connect('127.0.0.1', 5001)
def printlog():
try:
fd = open(os.path.join(base_dir, r'ts/live.m3u8'))
line = fd.read()
if line.strip():
pushtoipfs(line.strip())
fd.close()
except Exception, e:
print str(e)
class MyEventHandler(pyinotify.ProcessEvent):
def process_IN_MODIFY(self, event):
try:
printlog()
except Exception, e:
print str(e)
def pushtoipfs(m3u8):
pattern = re.compile(r'#EXTINF.*\n(.*)')
result1 = pattern.findall(m3u8)
newts = 0
for item in result1:
if not ipfscache.has_key(item):
print('add to ipfs: %s'%item)
res = api.add(os.path.join(base_dir, 'ts',item))
ipfscache[item]='/ipfs/%s'%res['Hash']
newts +=1
m3u8 = m3u8.replace(item, ipfscache[item])
# lastTS = ipfscache[result1[-1]]
if newts > 0:
res = api.add_str(m3u8)
lastm3u8 = '/ipfs/%s'%res
# api.pubsub_pub('live', u'%s\n'%lastTS)
api.pubsub_pub('livem3u8', u'%s\n'%lastm3u8)
def main():
printlog()
wm = pyinotify.WatchManager()
wm.add_watch(os.path.join(base_dir, r'ffmpeg.log'), pyinotify.ALL_EVENTS, rec=True)
eh = MyEventHandler()
# notifier
notifier = pyinotify.Notifier(wm, eh)
notifier.loop()
if __name__ == '__main__':
main()
<html lang="">
<head>
<title>LIVE ipfstitle>
<meta charset="utf-8">
<link href="video-js.min.css" rel="stylesheet">
<style type="text/css">body{margin:0;padding:0;}#live{display: none}style>
head>
<body>
<video id="live" class="video-js" controls preload="none" width="640" height="360" autoplay style="width: 100%; height: 100vh;">
video>
<script src="jquery-3.3.1.min.js">script>
<script src="ipfs.min.js">script>
<script src="video.min.js">script>
<script src="videojs-http-streaming.js">script>
<script>
let url;
let ws;
let ipns;
let isload = 0;
let isplayer = 0;
videojs.Hls.xhr.beforeRequest = (options) => {
if(options.responseType !== 'arraybuffer'){
options.uri = url;
}
node.pubsub.peers('live_m3u8_'+ipns, (err, peerIds) => {
if(peerIds.length<1){
for (let i = ws.length - 1; i >= 0; i--) node.swarm.connect(ws[i]);
}
});
return options;
};
const node = new window.Ipfs({
repo: '/ipfs-' + Math.random(),
config: {
Addresses: {
Swarm: ['/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star/']
}
},
EXPERIMENTAL: {pubsub: true}
});
node.on('ready', () => {
isload = 1;
for (let i = ws.length - 1; i >= 0; i--) node.swarm.connect(ws[i]);
node.pubsub.subscribe('live_m3u8_'+ipns,(msg) => {
url = msg.data.toString();
if(isplayer===0){
isplayer = 1;
const video = document.getElementById('live');
const source = document.createElement("source");
source.src = url;
source.type = 'application/x-mpegURL';
video.appendChild(source);
$('#live').css({display:'block'});
const player = videojs('live');
setTimeout(()=>{
player.play();
},500);
}
});
});
$(() =>{
$.get('config.json',(config)=>{
ws = config.ws;
ipns = config.ipns;
});
setTimeout(()=>{
if(isload===0){
// window.location.reload()
}
},15000)
})
script>
body>
html>
其中config.json 文件内容:
{
"version":0.1,
"ipns":"QmerPfuRp964AoNGMWBts9TQBMoPTYQpeq2rSAt3aywS6m",
"ws":[
"/ip4/172.16.10.170/tcp/9999/ws/ipfs/QmerPfuRp964AoNGMWBts9TQBMoPTYQpeq2rSAt3aywS6m"
]
}
其中这里面的ipns表示我key的地址,将整个文件夹编辑好,推送至ipfs,就可以通过任意公共网关加载视频了。
尝试推送一部电影
目前有比较高的延迟我自己测试有缓冲40秒左右