Skip to content

Instantly share code, notes, and snippets.

@why404
Last active July 10, 2020 07:45
Show Gist options
  • Save why404/706a196dab8279c2a49d to your computer and use it in GitHub Desktop.
Save why404/706a196dab8279c2a49d to your computer and use it in GitHub Desktop.

Stream API

特别说明

  • 最新功能体验可以将 API Host 改为 pili-lte.qiniuapi.com

新建流

请求包:

POST /v1/streams
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>
Content-Type: application/json
{
	"hub": <HubName:string>, # 必须
	"title": <StreamTitle:string>,  #可选,流名称,缺省由系统自动生成一个
	"publishKey": <StreamPublishKey:string>,  #可选,设定一个密钥用于生成动态 publishToken 进行推流鉴权,缺省为系统自动生成
	"publishSecurity": <PublishSecurity:string> #可选,值为 "dynamic" 或 "static"
}

返回包:

200 OK
Content-Type: application/json
{
    "id": "<StreamId>",
    "hub": "<HubName>",
    "title": "<StreamTitle>",
    "publishKey": "<StreamPublishKey>",
    "publishSecurity": "<PublishSecurity>",
    "disabled": false,
    "profiles": ["<ProfileName>"],
    "hosts": {
        "publish": {
            "rtmp": "<RtmpPublishHost>"
        },
        "live": {
            "rtmp": "<RtmpPlayHost>",
            "hls": "<HLSPlayHost>",
            "hdl": "<HDLPlayHost>"
        },
        "playback": {
            "hls"; "<HLSPlaybackHost>"
        }
    }
}
  • <StreamId> = <Zone>.<HubName>.<StreamTitle>

  • <StreamTitle> 格式要求:^[a-zA-Z0-9_-]{4,100}$

  • <PublishSecurity>: 可选,推流的安全策略,值为 dynamicstatic,缺省是 dynamic

  • RTMP 推流地址格式是:rtmp://<hosts["publish"]["rtmp"]>/<HubName>/<StreamTitle>

  • <PublishSecurity>="static" 时,RTMP 推流地址格式是:rtmp://<hosts["publish"]["rtmp"]>/<HubName>/<StreamTitle>?key=<StreamPublishKey>

  • <PublishSecurity>="dynamic" 时,RTMP 推流地址格式是:rtmp://<hosts["publish"]["rtmp"]>/<HubName>/<StreamTitle>?nonce=<Nonce>&token=<PublishToken><Nonce> 有效值为比上次推流使用的 <Nonce> 大的任意值。

  • <PublishToken> = hmac_sha1("/<HubName>/<StreamTitle>?nonce=<Nonce>&<OtherKey>=<OtherValue>", "<StreamPublishKey>")

  • RTMP 直播播放地址格式是:rtmp://<hosts["live"]["rtmp"]>/<HubName>/<StreamTitle>[@<ProfileName>]

  • HTTP FLV 直播播放地址格式是:http://<hosts["live"]["hdl"]>/<HubName>/<StreamTitle>[@<ProfileName>].flv

  • HLS 直播播放地址格式是:http://<hosts["live"]["hls"]>/<HubName>/<StreamTitle>[@<ProfileName>].m3u8

  • HLS 回放点播地址格式是:http://<hosts["playback"]["hls"]>/<HubName>/<StreamTitle>[@<ProfileName>].m3u8?start=<startTime>&end=<endTime>

  • [@<ProfileName>] 表示可选,<ProfileName> 表示在云端配置的实时转码规格别名(例如:720p, 480p),需要额外申请开通。

查询流

请求包:

GET /v1/streams/<StreamId>
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>

返回包:

200 OK
Content-Type: application/json
{
    "id": "<StreamId>",
    "hub": "<HubName>",
    "title": "<StreamTitle>",
    "publishKey": "<StreamPublishKey>",
    "publishSecurity": "<PublishSecurity>",
    "disabled": false,
    "profiles": ["<ProfileName>"],
    "hosts": {
        "publish": {
            "rtmp": "<RtmpPublishHost>"
        },
        "live": {
            "rtmp": "<RtmpPlayHost>",
            "hls": "<HLSPlayHost>",
            "hdl": "<HDLPlayHost>"
        },
        "playback": {
            "hls"; "<HLSPlaybackHost>"
        }
    }
}

列出流

请求包:

  • <HubName>: 必选。
  • <Status>: 可选。只能使用connected。
  • <StartMark>: 可选。如果未指定则从头开始。
  • <LimitCount>: 可选。如果未指定则用内部的一个上限值(比如1000)。
GET /v1/streams?status=<Status>&hub=<HubName>&marker=<StartMarker>&limit=<LimitCount>&title=<TitlePrefix>
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>

返回包:

200 OK
Content-Type: application/json
{
	"marker": <NextMarker:string>,
	"items": [
        {
        	"id": "<StreamId>",
        	"hub": "<HubName>",
        	"title": "<StreamTitle>",
        	"publishKey": "<StreamPublishKey>",
        	"publishSecurity": "<PublishSecurity>",
            "disabled": false,
            "profiles": ["<ProfileName>"],
            "hosts": {
                "publish": {
                    "rtmp": "<RtmpPublishHost>"
                },
                "live": {
                    "rtmp": "<RtmpPlayHost>",
                    "hls": "<HLSPlayHost>",
                    "hdl": "<HDLPlayHost>"
                },
                "playback": {
                    "hls"; "<HLSPlaybackHost>"
                }
            }
		}
	]
}

获取流状态

请求包:

GET /v1/streams/<StreamId>/status
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>

返回包:

  • "<Status>"
    • connected
    • disconnected
200 OK
Content-Type: application/json
{
    "addr": "<RemoteAddress>",
    "startFrom": "<StartFromTime>",
    "bytesPerSecond": <CurrentBytesPerSecond>,
    "framesPerSecond": {
        "audio": <AudioFramesPerSecond>,
        "video": <VideoFramesPerSecond>,
        "data": <DataFramesPerSecond>
    },
    "status": "<Status>"
}

更新流

请求包:

POST /v1/streams/<StreamId>
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>
Content-Type: application/json
{
	"publishKey": <StreamPublishKey:string>,
	"publishSecurity": <PublishSecurity:string>,
	"disabled": true // or false
}

返回包:

200 OK
Content-Type: application/json
{
    "id": "<StreamId>",
    "hub": "<HubName>",
    "title": "<StreamTitle>",
    "publishKey": "<StreamPublishKey>",
    "publishSecurity": "<PublishSecurity>",
    "disabled": true,
    "profiles": ["<ProfileName>"],
    "hosts": {
        "publish": {
            "rtmp": "<RtmpPublishHost>"
        },
        "live": {
            "rtmp": "<RtmpPlayHost>",
            "hls": "<HLSPlayHost>",
            "hdl": "<HDLPlayHost>"
        },
        "playback": {
            "hls"; "<HLSPlaybackHost>"
        }
    }
}

删除流

请求包:

DELETE /v1/streams/<StreamId>
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>

返回包:

204 No Content

获取分片

请求包:

GET /v1/streams/<StreamId>/segments?start=<StartUnixTimestamp>&end=<EndUnixTimestamp>&limit=<Limit>
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>
  • start - 可选,开始时间,UnixTimestamp 数值。
  • end - 可选,结束时间,UnixTimestamp 数值。

返回包:

200 OK
Content-Type: application/json
{
    "start": <StreamStartUnixTimestamp>,
    "end": <StreamEndUnixTimestamp>,
    "duration": <TotalDurationOfRequestSegments>,
    "segments": [
        {
            "start": <StartUnixTimestamp>,
            "end": <EndUnixTimestamp>
        },
        {
            "start": <StartUnixTimestamp>,
            "end": <EndUnixTimestamp>
        }
    ]
}

HLS 回放 URL 格式是:http://<hosts["playback"]["hls"]>/<HubName>/<StreamTitle>[@<ProfileName>].m3u8?start=<StartUnixTimestamp>&end=<EndUnixTimestamp>

抽帧截图

请求包:

POST /v1/streams/<StreamId>/snapshot
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>
{
    "name": "<SnapshotName>",
    "format": "<SnapshotFormat>",
    "time": <SnapAtUnixTimestamp>, // optional, default is now
    "notifyUrl": "<NotifyUrl>"
}

返回包:

200 OK
Content-Type: application/json
{
    "targetUrl": "<TargetUrl>",
    "persistentId": "<PersistentId>"
}

转存为其他格式的文件

请求包:

POST /v1/streams/<StreamId>/saveas
Host: pili.qiniuapi.com
Authorization: Qiniu <MacToken>
{
    "name": "<VideoName>",
    "notifyUrl": "<NotifyUrl>",
    "start": <StartUnixTimestamp>, // 可选,默认为流最开始的数据
    "end": <EndUnixTimestamp>,     // 可选,默认为流最后的数据
    "format": "<VideoFormat>"      // http://developer.qiniu.com/docs/v6/api/reference/fop/av/avthumb.html
}

返回包:

202 Accept
Content-Type: application/json
{
    "url": "<m3u8Url>",
    "targetUrl": "<TargetFileUrl>",
    "persistentId": <PersistentId>
}

Authorization: Qiniu MacToken

针对如下格式的一个 HTTP 请求:

<Method> <PathWithRawQuery>
<Header1>: <Value1>
<Header2>: <Value2>
...
Host: <Host>
...
Content-Type: <ContentType>
...
Authorization: Qiniu <AK>:<Sign>
...

<Body>

对于上面这样一个请求,我们构造如下这个待签名的 <Data>

<Method> <PathWithRawQuery>
Host: <Host>
Content-Type: <ContentType>

[<Body>] #这里的 <Body> 只有在 <ContentType> 存在且不为 application/octet-stream 时才签进去。

有了 <Data>,就可以计算对应的 <Sign>,如下:

<Sign> = urlsafe_base64(hmac_sha1(<SK>, <Data>))

一个正常的待签名的 <Data> 长如下样子:

POST /v1/streams
Host: pili.qiniuapi.com
Content-Type: application/json

{
    "title": 'abc',
    "hub": 'doretest001',
    "publishKey": 'aaaa-bbbb-cccc-dddd', 
    "publishSecurity": 'static'
}

Go 签名示例:

func MACToken(sk []byte, req *http.Request) ([]byte, error) {

	h := hmac.New(sha1.New, sk)

	u := req.URL
	data := req.Method + " " + u.Path
	if u.RawQuery != "" {
		data += "?" + u.RawQuery
	}
	io.WriteString(h, data + "\nHost: " + req.Host)

	ctType := req.Header.Get("Content-Type")
	if ctType != "" {
		io.WriteString(h, "\nContent-Type: " + ctType)
	}
	io.WriteString(h, "\n\n")

	if req.Body != nil && ctType != "" && ctType != "application/octet-stream" {
		s2, err2 := seekable.New(req)
		if err2 != nil {
			return nil, err2
		}
		h.Write(s2.Bytes())
	}

	return h.Sum(nil), nil
}
@qingfeng
Copy link

提问:

我生成的推流地址如下:
rtmp://pub.z1.glb.pili.qiniup.com/gouhuolive/5544ef69fb16df2e330001d7?key=e51a45450d680f32

下午 2015.5.19 13:25 完成了一次直播,但是想进行回放,用如下地址(时间戳已经计算过),并没有访问到可以回放的视频~求解

回放URL: http://hls1.z1.glb.pili.qiniuapi.com/gouhuolive/5544ef69fb16df2e330001d7.m3u8?start=1432013100&end=1432013700

@why404
Copy link
Author

why404 commented May 25, 2015

@qingfeng

每次推流结束后,该 stream 都会新增一条 segment 记录。

借助 GET /v1/streams/<StreamId>/segments 接口可以查询指定 stream 一共有多少次推流记录,以及每次推流的起始时间,该值有助于判断回放时该如何指定具体的有效区间。

例如,

假设 GET /v1/streams/<StreamId>/segments 接口返回 response 如下:

[
    {"start": 111, "end": "222"},
    {"start": 333, "end": "444"},
    {"start": 555, "end": "666"}
]

那说明该 stream 一共发生了 3 次推流行为。回放时,http://hls_playback_url?start=$start&end=$end 此回放 url 中,$start$end 取值范围可以是 111 - 666 之间的任意值。

请务必先调用 GET /v1/streams/<StreamId>/segments 先查询指定 stream 可以用于进行回放的区间。

该接口在各语言包的 sdk 中都有提供已经封装好了的 function 。

@why404
Copy link
Author

why404 commented May 25, 2015

API update news (05/26/2015):

  1. stream struct 单元新增 disabled (bool) 字段,用于标识 stream 是否被禁用。
  2. 更新流接口 - POST /v1/streams/<StreamId>,请求参数新增 disabled 字段,值为 true 时,表意为禁止该 stream, stream 一旦被禁用,推流和播放都会失效。
  3. 新增流状态查询接口 - GET /v1/streams/<StreamId>/status,用于查询 stream 状态。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment