├── .gitignore ├── LICENSE ├── README.md ├── example └── example.go └── pili ├── conf.go ├── credentials.go ├── hub.go ├── model.go ├── rpc.go ├── stream.go └── transport.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pili Engineering, Qiniu Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Pili Streaming Cloud server-side library for Golang](#pili-streaming-cloud-server-side-library-for-golang) 6 | - [Features](#features) 7 | - [Contents](#contents) 8 | - [Installation](#installation) 9 | - [Usage](#usage) 10 | - [Configuration](#configuration) 11 | - [Hub](#hub) 12 | - [Instantiate a Pili Hub object](#instantiate-a-pili-hub-object) 13 | - [Create a new Stream](#create-a-new-stream) 14 | - [Get a Stream](#get-a-stream) 15 | - [List streams](#list-streams) 16 | - [Stream](#stream) 17 | - [To JSON String](#to-json-string) 18 | - [Update a Stream](#update-a-stream) 19 | - [Disable a stream](#disable-a-stream) 20 | - [Disable a stream with disableTill](#disable-a-stream-with-disabletill) 21 | - [Enable a Stream](#enable-a-stream) 22 | - [Generate RTMP publish URL](#generate-rtmp-publish-url) 23 | - [Generate RTMP live play URLs](#generate-rtmp-live-play-urls) 24 | - [Generate HLS play live URLs](#generate-hls-play-live-urls) 25 | - [Generate Http-Flv live play URLs](#generate-http-flv-live-play-urls) 26 | - [Get Stream status](#get-stream-status) 27 | - [Get Stream segments](#get-stream-segments) 28 | - [Generate HLS playback URLs](#generate-hls-playback-urls) 29 | - [Save Stream as a file](#save-stream-as-a-file) 30 | - [Snapshot Stream](#snapshot-stream) 31 | - [Delete a Stream](#delete-a-stream) 32 | - [History](#history) 33 | 34 | 35 | 36 | # Pili Streaming Cloud server-side library for Golang 37 | 38 | ## Features 39 | 40 | - Stream Create,Get,List 41 | - [x] hub.CreateStream(options={Title,PublishKey,PublishSecurity}) 42 | - [x] hub.GetStream(stream.Id) 43 | - [x] hub.ListStreams(options={Status,Marker,Limit,Title}) 44 | - Stream operations else 45 | - [x] stream.ToJSONString() 46 | - [x] stream.Status() 47 | - [x] stream.Update(options={PublishKey,PublishSecurity}) 48 | - [x] stream.Refresh() 49 | - [x] stream.RtmpPublishUrl() 50 | - [x] stream.RtmpLiveUrls() 51 | - [x] stream.HlsLiveUrls() 52 | - [x] stream.HttpFlvLiveUrls() 53 | - [x] stream.HlsPlaybackUrls(start, end int64) 54 | - [x] stream.Segments(options={Start,End, Limit}) 55 | - [x] stream.SaveAs(name,format string, start,end int64, options={notifyUrl}) 56 | - [x] stream.Snapshot(name,format string, options={time, notifyUrl}) 57 | - [x] stream.Delete() 58 | 59 | ## Contents 60 | 61 | - [Installation](#installation) 62 | - [Usage](#usage) 63 | - [Configuration](#configuration) 64 | - [Hub](#hub) 65 | - [Instantiate a Pili Hub object](#instantiate-a-pili-hub-object) 66 | - [Create a new Stream](#create-a-new-stream) 67 | - [Get a Stream](#get-a-stream) 68 | - [List Streams](#List-streams) 69 | - [Stream](#stream) 70 | - [To JSON string](#to-json-string) 71 | - [Update a Stream](#update-a-stream) 72 | - [Disable a Stream](#disable-a-stream) 73 | - [Enable a Stream](#enable-a-stream) 74 | - [Generate RTMP publish URL](#generate-rtmp-publish-url) 75 | - [Generate RTMP live play URLs](#generate-rtmp-live-play-urls) 76 | - [Generate HLS live play URLs](generate-hls-live-play-urls) 77 | - [Generate Http-Flv live play URLs](generate-http-flv-live-play-urls) 78 | - [Get Stream status](#get-stream-status) 79 | - [Get Stream segments](#get-stream-segments) 80 | - [Generate HLS playback URLs](generate-hls-playback-urls) 81 | - [Save Stream as a file](#save-stream-as-a-file) 82 | - [Snapshot Stream](#snapshot-stream) 83 | - [Delete a Stream](#delete-a-stream) 84 | - [History](#history) 85 | 86 | 87 | ## Installation 88 | 89 | before next step, install git. 90 | 91 | ``` 92 | // install latest version 93 | $ go get github.com/pili-engineering/pili-sdk-go/pili 94 | ``` 95 | 96 | ## Usage 97 | 98 | ### Configuration 99 | 100 | ```go 101 | package main 102 | 103 | import ( 104 | "github.com/pili-engineering/pili-sdk-go/pili" 105 | "fmt" 106 | // ... 107 | ) 108 | 109 | const ( 110 | ACCESS_KEY = "Qiniu_AccessKey" 111 | SECRET_KEY = "Qiniu_SecretKey" 112 | HUB_NAME = "Pili_HubName" // The Hub must be exists before use 113 | ) 114 | 115 | func main() { 116 | 117 | // Change API host as necessary 118 | // 119 | // pili.qiniuapi.com as default 120 | // pili-lte.qiniuapi.com is the latest RC version 121 | // 122 | // pili.API_HOST = "pili.qiniuapi.com" // default 123 | 124 | } 125 | ``` 126 | 127 | ### Hub 128 | 129 | #### Instantiate a Pili Hub object 130 | 131 | ```go 132 | func main() { 133 | 134 | credentials := pili.NewCredentials(ACCESS_KEY, SECRET_KEY) 135 | hub := pili.NewHub(credentials, HUB_NAME) 136 | 137 | // ... 138 | } 139 | ``` 140 | 141 | #### Create a new Stream 142 | 143 | ```go 144 | options := pili.OptionalArguments{ // optional 145 | Title: "stream_title", // optional, auto-generated as default 146 | PublishKey: "some_secret_words", // optional, auto-generated as default 147 | PublishSecurity: "dynamic", // optional, can be "dynamic" or "static", "dynamic" as default 148 | } 149 | stream, err := hub.CreateStream(options) 150 | if err != nil { 151 | fmt.Println("Error:", err) 152 | } 153 | fmt.Println("CreateStream:\n", stream) 154 | /* 155 | { 156 | 0xc208036018 157 | Id: z1.hub1.stream_title 158 | CreatedAt: 2015-08-22 15:37:20.397 +0800 CST 159 | UpdatedAt: 2015-08-24 09:41:55.32 +0800 CST 160 | Title: stream_title 161 | Hub: hub1 162 | Disabled: false 163 | PublishKey: some_secret_words 164 | PublishSecurity: dynamic 165 | Profiles: [] 166 | Hosts: { 167 | Publish: map[ 168 | rtmp:ec2s3f5.publish.z1.pili.qiniup.com 169 | ] 170 | Live: map[ 171 | http:ec2s3f5.live1-http.z1.pili.qiniucdn.com 172 | rtmp:ec2s3f5.live1-rtmp.z1.pili.qiniucdn.com 173 | ] 174 | Playback: map[ 175 | http:ec2s3f5.playback1.z1.pili.qiniucdn.com 176 | ] 177 | } 178 | } 179 | */ 180 | ``` 181 | 182 | #### Get a Stream 183 | 184 | ```go 185 | stream, err = hub.GetStream(stream.Id) 186 | if err != nil { 187 | fmt.Println("Error:", err) 188 | } 189 | fmt.Println("GetStream:\n", stream) 190 | /* 191 | { 192 | 0xc208036018 193 | Id: z1.hub1.stream_title 194 | CreatedAt: 2015-08-22 15:37:20.397 +0800 CST 195 | UpdatedAt: 2015-08-24 09:41:55.32 +0800 CST 196 | Title: stream_title 197 | Hub: hub1 198 | Disabled: false 199 | PublishKey: some_secret_words 200 | PublishSecurity: dynamic 201 | Profiles: [] 202 | Hosts: { 203 | Publish: map[ 204 | rtmp:ec2s3f5.publish.z1.pili.qiniup.com 205 | ] 206 | Live: map[ 207 | http:ec2s3f5.live1-http.z1.pili.qiniucdn.com 208 | rtmp:ec2s3f5.live1-rtmp.z1.pili.qiniucdn.com 209 | ] 210 | Playback: map[ 211 | http:ec2s3f5.playback1.z1.pili.qiniucdn.com 212 | ] 213 | } 214 | } 215 | */ 216 | ``` 217 | 218 | #### List streams 219 | 220 | ```go 221 | options = pili.OptionalArguments{ // optional 222 | // Status: "connected", // optional 223 | Marker: "", // optional, returned by server response 224 | Limit: 50, // optional 225 | Title: "prefix_", // optional, title prefix 226 | } 227 | listResult, err := hub.ListStreams(options) 228 | if err != nil { 229 | fmt.Println("Error:", err) 230 | } 231 | fmt.Println("ListStreams:\n", listResult) 232 | for _, stream := range listResult.Items { 233 | fmt.Println("Stream:\n", stream) 234 | } 235 | /* 236 | {1 [0xc208036018]} 237 | &{ 238 | 0xc208036018 239 | Id: z1.hub1.stream_title 240 | CreatedAt: 2015-08-22 15:37:20.397 +0800 CST 241 | UpdatedAt: 2015-08-24 09:41:55.32 +0800 CST 242 | Title: stream_title 243 | Hub: hub1 244 | Disabled: false 245 | PublishKey: some_secret_words 246 | PublishSecurity: dynamic 247 | Profiles: [] 248 | Hosts: { 249 | Publish: map[ 250 | rtmp:ec2s3f5.publish.z1.pili.qiniup.com 251 | ] 252 | Live: map[ 253 | http:ec2s3f5.live1-http.z1.pili.qiniucdn.com 254 | rtmp:ec2s3f5.live1-rtmp.z1.pili.qiniucdn.com 255 | ] 256 | Playback: map[ 257 | http:ec2s3f5.playback1.z1.pili.qiniucdn.com 258 | ] 259 | } 260 | } 261 | */ 262 | ``` 263 | 264 | ### Stream 265 | 266 | #### To JSON String 267 | ```go 268 | streamJson, err := stream.ToJSONString() 269 | if err != nil { 270 | fmt.Println("Error:", err) 271 | } 272 | fmt.Println("Stream ToJSONString:\n", streamJson) 273 | /* 274 | { 275 | "id":"z1.hub1.stream_title", 276 | "createdAt":"2015-08-22T15:37:20.397+08:00", 277 | "updatedAt":"2015-08-24T09:41:55.32+08:00", 278 | "title":"stream_title", 279 | "hub":"hub1", 280 | "disabled":false, 281 | "publishKey":"some_secret_words", 282 | "publishSecurity":"dynamic", 283 | "hosts":{ 284 | "publish":{ 285 | "rtmp":"ec2s3f5.publish.z1.pili.qiniup.com" 286 | }, 287 | "live":{ 288 | "http":"ec2s3f5.live1-http.z1.pili.qiniucdn.com", 289 | "rtmp":"ec2s3f5.live1-rtmp.z1.pili.qiniucdn.com" 290 | }, 291 | "playback":{ 292 | "http":"ec2s3f5.playback1.z1.pili.qiniucdn.com" 293 | } 294 | } 295 | } 296 | */ 297 | ``` 298 | 299 | #### Update a Stream 300 | 301 | ```go 302 | stream.PublishKey = "new_secret_words" // optional 303 | stream.PublishSecurity = "static" // optional 304 | stream, err = stream.Update() 305 | if err != nil { 306 | fmt.Println("Error:", err) 307 | } 308 | fmt.Println("Stream Updated:\n", stream) 309 | /* 310 | { 311 | 0xc208036018 312 | Id: z1.hub1.stream_title 313 | CreatedAt: 2015-08-22 15:37:20.397 +0800 CST 314 | UpdatedAt: 2015-08-24 09:41:55.32 +0800 CST 315 | Title: stream_title 316 | Hub: hub1 317 | Disabled: false 318 | PublishKey: new_secret_words 319 | PublishSecurity: static 320 | Profiles: [] 321 | Hosts: { 322 | Publish: map[ 323 | rtmp:ec2s3f5.publish.z1.pili.qiniup.com 324 | ] 325 | Live: map[ 326 | http:ec2s3f5.live1-http.z1.pili.qiniucdn.com 327 | rtmp:ec2s3f5.live1-rtmp.z1.pili.qiniucdn.com 328 | ] 329 | Playback: map[ 330 | http:ec2s3f5.playback1.z1.pili.qiniucdn.com 331 | ] 332 | } 333 | } 334 | */ 335 | ``` 336 | 337 | #### Disable a stream 338 | 339 | ```go 340 | stream, err = stream.Disable() 341 | if err != nil { 342 | fmt.Println("Error:", err) 343 | } 344 | fmt.Println("Stream Disabled:\n", stream.Disabled) 345 | /* 346 | true 347 | */ 348 | ``` 349 | 350 | #### Disable a stream with disableTill 351 | 352 | ```go 353 | err := stream.DisableTill(time.Now().Add(time.Hour)) 354 | if err != nil { 355 | fmt.Println("Error:", err) 356 | } 357 | ``` 358 | 359 | #### Enable a Stream 360 | 361 | ```go 362 | stream, err = stream.Enable() 363 | if err != nil { 364 | fmt.Println("Error:", err) 365 | } 366 | fmt.Println("Stream Enabled:\n", stream.Disabled) 367 | /* 368 | false 369 | */ 370 | ``` 371 | 372 | #### Generate RTMP publish URL 373 | 374 | ```go 375 | url := stream.RtmpPublishUrl() 376 | fmt.Println("Stream RtmpPublishUrl:\n", url) 377 | /* 378 | rtmp://ec2s3f5.publish.z1.pili.qiniup.com/hub1/stream_title?key=new_secret_words 379 | */ 380 | ``` 381 | 382 | #### Generate RTMP live play URLs 383 | 384 | ```go 385 | urls, err := stream.RtmpLiveUrls() 386 | if err != nil { 387 | fmt.Println("Error:", err) 388 | } 389 | fmt.Println("RtmpLiveUrls:", urls) 390 | /* 391 | map[ORIGIN:rtmp://ec2s3f5.live1-rtmp.z1.pili.qiniucdn.com/hub1/stream_title] 392 | */ 393 | ``` 394 | 395 | #### Generate HLS play live URLs 396 | 397 | ```go 398 | urls, err = stream.HlsLiveUrls() 399 | if err != nil { 400 | fmt.Println("Error:", err) 401 | } 402 | fmt.Println("HlsLiveUrls:", urls) 403 | /* 404 | map[ORIGIN:http://ec2s3f5.live1-http.z1.pili.qiniucdn.com/hub1/stream_title.m3u8] 405 | */ 406 | ``` 407 | 408 | #### Generate Http-Flv live play URLs 409 | 410 | ```go 411 | urls, err = stream.HttpFlvLiveUrls() 412 | if err != nil { 413 | fmt.Println("Error:", err) 414 | } 415 | fmt.Println("HttpFlvLiveUrls:", urls) 416 | /* 417 | map[ORIGIN:http://ec2s3f5.live1-http.z1.pili.qiniucdn.com/hub1/stream_title.flv] 418 | */ 419 | ``` 420 | 421 | #### Get Stream status 422 | 423 | ```go 424 | streamStatus, err := stream.Status() 425 | if err != nil { 426 | fmt.Println("Error:", err) 427 | } 428 | fmt.Println("Stream Status:\n", streamStatus) 429 | /* 430 | { 431 | Addr: 114.81.254.172:36317 432 | Status: connected 433 | BytesPerSecond: 16870.200000000001 434 | FramesPerSecond: { 435 | Audio: 42.200000000000003 436 | Video: 14.733333333333333 437 | Data: 0.066666666666666666 438 | } 439 | } 440 | */ 441 | ``` 442 | 443 | #### Get Stream segments 444 | 445 | ```go 446 | options = pili.OptionalArguments{ // optional 447 | Start: 1440379800, // optional, in second, unix timestamp 448 | End: 1440479880, // optional, in second, unix timestamp 449 | Limit: 20, // optional, uint 450 | } 451 | segments, err := stream.Segments(options) 452 | if err != nil { 453 | fmt.Println("Error:", err) 454 | } 455 | fmt.Println("Segments:\n", segments) 456 | /* 457 | {[0xc20800b2c0 0xc20800b320 0xc20800b350 0xc20800b380 0xc20800b3b0 0xc20800b3e0]} 458 | */ 459 | ``` 460 | 461 | #### Generate HLS playback URLs 462 | 463 | ```go 464 | start := 1440379847 465 | end := 1440379857 466 | urls, err = stream.HlsPlaybackUrls(int64(start), int64(end)) 467 | if err != nil { 468 | fmt.Println("Error:", err) 469 | } 470 | fmt.Println("HlsPlaybackUrls:", urls) 471 | /* 472 | map[ORIGIN:http://ec2s3f5.playback1.z1.pili.qiniucdn.com/hub1/stream_title.m3u8?start=1440379847&end=1440379857] 473 | */ 474 | ``` 475 | 476 | #### Save Stream as a file 477 | 478 | ```go 479 | name := "fileName.mp4" // required, string 480 | start = 1440379847 // required, int64, in second, unix timestamp 481 | end = 1440379857 // required, int64, in second, unix timestamp 482 | format := "mp4" // optional, string 483 | options = pili.OptionalArguments{ 484 | NotifyUrl: "http://remote_callback_url", 485 | UserPipeline: "user_pipeline", 486 | } // optional 487 | saveAsRes, err := stream.SaveAs(name, format, int64(start), int64(end), options) 488 | if err != nil { 489 | fmt.Println("Error:", err) 490 | } 491 | fmt.Println("Stream save as:\n", saveAsRes) 492 | /* 493 | { 494 | Url: http://ec2s3f5.vod1.z1.pili.qiniucdn.com/recordings/z1.hub1.stream_title/fileName.m3u8 495 | TargetUrl: http://ec2s3f5.vod1.z1.pili.qiniucdn.com/recordings/z1.hub1.stream_title/fileName.mp4 496 | PersistentId: z1.55da7715f51b82403b01e985 497 | } 498 | */ 499 | ``` 500 | 501 | While invoking `saveAs()` and `snapshot()`, you can get processing state via Qiniu FOP Service using `persistentId`. 502 | API: `curl -D GET http://api.qiniu.com/status/get/prefop?id={PersistentId}` 503 | Doc reference: 504 | 505 | #### Snapshot Stream 506 | 507 | ```go 508 | name = "fileName.jpg" // required, string 509 | format = "jpg" // required, string 510 | options = pili.OptionalArguments{ 511 | Time: 1440379847, // optional, int64, in second, unit timestamp 512 | NotifyUrl: "http://remote_callback_url", 513 | } // optional 514 | snapshotRes, err := stream.Snapshot(name, format, options) 515 | if err != nil { 516 | fmt.Println("Error:", err) 517 | } 518 | fmt.Println("Stream Snapshot:\n", snapshotRes) 519 | /* 520 | { 521 | TargetUrl: http://ec2s3f5.static1.z1.pili.qiniucdn.com/snapshots/z1.hub1.stream_title/fileName.jpg 522 | PersistentId: z1.55da7716f51b82403b01e986 523 | } 524 | */ 525 | ``` 526 | 527 | #### Delete a Stream 528 | 529 | ```go 530 | deleteResult, err := stream.Delete() 531 | if err != nil { 532 | fmt.Println("Error:", err) 533 | } 534 | fmt.Println("Stream Deleted:\n", deleteResult) 535 | /* 536 | 537 | */ 538 | ``` 539 | 540 | ## History 541 | 542 | - 1.5.3 543 | - Add UserPipeline in SaveAs 544 | - 1.5.2 545 | - Use SaveAs in HlsPlaybackUrls 546 | - 1.5.1 547 | - Update hub.ListStreams(options={Status, Marker,Limit, Title}) 548 | 549 | - 1.5.0 550 | - Add stream.HttpFlvLiveUrls() 551 | - Add stream.Snapshot(name,format string, options={time, notifyUrl}) 552 | 553 | - 1.3.1 554 | - Update stream.Update() logic 555 | 556 | - 1.3.0 557 | - Add stream.SaveAs(name,format string, start,end int64, options={notifyUrl}) 558 | 559 | - 1.2.0 560 | - Add Stream operations 561 | - stream.ToJSONString() 562 | - stream.Update(options={PublishKey,PublishSecurity}) 563 | - stream.Disable() 564 | - stream.Enable() 565 | - stream.RtmpPublishUrl() 566 | - stream.RtmpLiveUrls() 567 | - stream.HlsLiveUrls() 568 | - stream.Status() 569 | - stream.Segments(options={Start,End, Limit}) 570 | - stream.HlsPlaybackUrls(start, end int64) 571 | - stream.Delete() 572 | - Update hub functions 573 | - hub.CreateStream(options={Title,PublishKey,PublishSecurity}) 574 | - hub.GetStream(stream.Id) 575 | - hub.ListStreams(options={Marker,Limit, Title}) 576 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "../pili" // or "github.com/pili-engineering/pili-sdk-go/pili" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | const ( 10 | 11 | // Replace with your keys here 12 | ACCESS_KEY = "Qiniu_AccessKey" 13 | SECRET_KEY = "Qiniu_SecretKey" 14 | 15 | // The Hub must be exists before use 16 | HUB_NAME = "Pili_Hub_Name" 17 | ) 18 | 19 | func main() { 20 | 21 | // Change API host as necessary 22 | // 23 | // pili.qiniuapi.com as default 24 | // pili-lte.qiniuapi.com is the latest RC version 25 | // 26 | // pili.API_HOST = "pili.qiniuapi.com" // default 27 | 28 | // Instantiate a Pili Hub object 29 | credentials := pili.NewCredentials(ACCESS_KEY, SECRET_KEY) 30 | hub := pili.NewHub(credentials, HUB_NAME) 31 | 32 | // Create a new stream 33 | options := pili.OptionalArguments{ // optional 34 | Title: "stream_name", // optional, auto-generated as default 35 | PublishKey: "some_secret_words", // optional, auto-generated as default 36 | PublishSecurity: "dynamic", // optional, can be "dynamic" or "static", "dynamic" as default 37 | } 38 | stream, err := hub.CreateStream(options) 39 | if err != nil { 40 | fmt.Println("Error:", err) 41 | } 42 | fmt.Println("CreateStream:\n", stream) 43 | 44 | // Get a stream 45 | stream, err = hub.GetStream(stream.Id) 46 | if err != nil { 47 | fmt.Println("Error:", err) 48 | } 49 | fmt.Println("GetStream:\n", stream) 50 | 51 | // List streams 52 | options = pili.OptionalArguments{ // optional 53 | //Status: "connected", // optional 54 | //Marker: "", // optional, returned by server response 55 | //Limit: 50, // optional 56 | //Title: "", // optional, title prefix 57 | } 58 | listResult, err := hub.ListStreams(options) 59 | if err != nil { 60 | fmt.Println("Error:", err) 61 | } 62 | fmt.Println("ListStreams:\n", listResult) 63 | for _, stream := range listResult.Items { 64 | fmt.Println("Stream:\n", stream) 65 | } 66 | 67 | // To JSON String 68 | streamJson, err := stream.ToJSONString() 69 | if err != nil { 70 | fmt.Println("Error:", err) 71 | } 72 | fmt.Println("Stream ToJSONString:\n", streamJson) 73 | 74 | // Update a stream 75 | stream.PublishKey = "new_secret_words" // optional 76 | stream.PublishSecurity = "static" // optional 77 | stream, err = stream.Update() 78 | if err != nil { 79 | fmt.Println("Error:", err) 80 | } 81 | fmt.Println("Stream Updated:\n", stream) 82 | 83 | // Disable a stream 84 | stream, err = stream.Disable() 85 | if err != nil { 86 | fmt.Println("Error:", err) 87 | } 88 | fmt.Println("Stream Disabled:\n", stream) 89 | 90 | // Enable a stream 91 | stream, err = stream.Enable() 92 | if err != nil { 93 | fmt.Println("Error:", err) 94 | } 95 | fmt.Println("Stream Enabled:\n", stream) 96 | 97 | // Disable a stream with disableTill 98 | err = stream.DisableTill(time.Now().Add(time.Hour)) 99 | if err != nil { 100 | fmt.Println("Error:", err) 101 | } 102 | 103 | stream, err = stream.Enable() 104 | if err != nil { 105 | fmt.Println("Error:", err) 106 | } 107 | fmt.Println("Stream Enabled:\n", stream) 108 | 109 | // Generate RTMP publish URL 110 | url := stream.RtmpPublishUrl() 111 | fmt.Println("Stream RtmpPublishUrl:\n", url) 112 | 113 | // Generate RTMP live play URLs 114 | urls, err := stream.RtmpLiveUrls() 115 | if err != nil { 116 | fmt.Println("Error:", err) 117 | } 118 | fmt.Println("RtmpLiveUrls:", urls) 119 | for k, v := range urls { 120 | fmt.Printf("%s:%s\n", k, v) 121 | } 122 | 123 | // Generate HLS live play URLs 124 | urls, err = stream.HlsLiveUrls() 125 | if err != nil { 126 | fmt.Println("Error:", err) 127 | } 128 | fmt.Println("HlsLiveUrls:", urls) 129 | for k, v := range urls { 130 | fmt.Printf("%s:%s\n", k, v) 131 | } 132 | 133 | // Generate Http-Flv live play URLs 134 | urls, err = stream.HttpFlvLiveUrls() 135 | if err != nil { 136 | fmt.Println("Error:", err) 137 | } 138 | fmt.Println("HttpFlvLiveUrls:", urls) 139 | for k, v := range urls { 140 | fmt.Printf("%s:%s\n", k, v) 141 | } 142 | 143 | // Get stream status 144 | streamStatus, err := stream.Status() 145 | if err != nil { 146 | fmt.Println("Error:", err) 147 | } 148 | fmt.Println("Stream Status:\n", streamStatus) 149 | 150 | // Get stream segments 151 | options = pili.OptionalArguments{ // optional 152 | Start: 1440379800, // optional, in second, unix timestamp 153 | End: 1440479880, // optional, in second, unix timestamp 154 | Limit: 20, // optional, uint 155 | } 156 | segments, err := stream.Segments(options) 157 | if err != nil { 158 | fmt.Println("Error:", err) 159 | } 160 | fmt.Println("Segments:\n", segments) 161 | 162 | // Generate HLS playback URLs 163 | start := 1440379847 164 | end := 1440379857 165 | urls, err = stream.HlsPlaybackUrls(int64(start), int64(end)) 166 | if err != nil { 167 | fmt.Println("Error:", err) 168 | } 169 | fmt.Println("HlsPlaybackUrls:", urls) 170 | for k, v := range urls { 171 | fmt.Printf("%s:%s\n", k, v) 172 | } 173 | 174 | // Save Stream as a file 175 | name := "fileName.mp4" // required, string 176 | start = 1440379847 // required, int64, in second, unix timestamp 177 | end = 1440379857 // required, int64, in second, unix timestamp 178 | format := "mp4" // optional, string 179 | options = pili.OptionalArguments{ 180 | NotifyUrl: "http://remote_callback_url", 181 | } // optional 182 | saveAsRes, err := stream.SaveAs(name, format, int64(start), int64(end), options) 183 | if err != nil { 184 | fmt.Println("Error:", err) 185 | } 186 | fmt.Println("Stream save as:\n", saveAsRes) 187 | 188 | // Snapshot Stream 189 | name = "fileName.jpg" // required, string 190 | format = "jpg" // required, string 191 | options = pili.OptionalArguments{ 192 | Time: 1440379847, // optional, int64, in second, unit timestamp 193 | NotifyUrl: "http://remote_callback_url", 194 | } // optional 195 | snapshotRes, err := stream.Snapshot(name, format, options) 196 | if err != nil { 197 | fmt.Println("Error:", err) 198 | } 199 | fmt.Println("Stream Snapshot:\n", snapshotRes) 200 | 201 | // Delete a stream 202 | deleteResult, err := stream.Delete() 203 | if err != nil { 204 | fmt.Println("Error:", err) 205 | } 206 | fmt.Println("Stream Deleted:\n", deleteResult) 207 | } 208 | -------------------------------------------------------------------------------- /pili/conf.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | const ( 9 | SDK_VERSION = "1.5.2" 10 | SDK_USER_AGENT = "pili-sdk-go" 11 | DEFAULT_API_VERSION = "v1" 12 | DEFAULT_API_HOST = "pili.qiniuapi.com" 13 | ORIGIN = "ORIGIN" 14 | ) 15 | 16 | var ( 17 | API_HOST string 18 | USE_HTTPS bool 19 | ) 20 | 21 | func UserAgent() string { 22 | return fmt.Sprintf("%s/%s %s %s/%s", SDK_USER_AGENT, SDK_VERSION, runtime.Version(), runtime.GOOS, runtime.GOARCH) 23 | } 24 | 25 | func getHttpScheme() (scheme string) { 26 | scheme = "http" 27 | if USE_HTTPS { 28 | scheme = "https" 29 | } 30 | return 31 | } 32 | 33 | func getApiHost() (host string) { 34 | host = DEFAULT_API_HOST 35 | if API_HOST != "" { 36 | host = API_HOST 37 | } 38 | return 39 | } 40 | 41 | func getApiBaseUrl() (url string) { 42 | return fmt.Sprintf("%s://%s/%s", getHttpScheme(), getApiHost(), DEFAULT_API_VERSION) 43 | } 44 | -------------------------------------------------------------------------------- /pili/credentials.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "github.com/qiniu/bytes/seekable" 8 | "io" 9 | "net/http" 10 | ) 11 | 12 | func NewCredentials(accessKey, secretKey string) *Credentials { 13 | return &Credentials{accessKey, secretKey} 14 | } 15 | 16 | type Credentials struct { 17 | AccessKey string 18 | SecretKey string 19 | } 20 | 21 | func (c *Credentials) MACToken(req *http.Request) (token string, err error) { 22 | 23 | h := hmac.New(sha1.New, []byte(c.SecretKey)) 24 | 25 | u := req.URL 26 | data := req.Method + " " + u.Path 27 | if u.RawQuery != "" { 28 | data += "?" + u.RawQuery 29 | } 30 | io.WriteString(h, data+"\nHost: "+req.Host) 31 | 32 | ctType := req.Header.Get("Content-Type") 33 | if ctType != "" { 34 | io.WriteString(h, "\nContent-Type: "+ctType) 35 | } 36 | io.WriteString(h, "\n\n") 37 | 38 | if req.Body != nil && ctType != "" && ctType != "application/octet-stream" { 39 | s2, err2 := seekable.New(req) 40 | if err2 != nil { 41 | return "", err2 42 | } 43 | h.Write(s2.Bytes()) 44 | } 45 | 46 | sign := base64.URLEncoding.EncodeToString(h.Sum(nil)) 47 | token = c.AccessKey + ":" + sign 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /pili/hub.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Hub struct { 8 | rpc *RPC 9 | hubName string 10 | } 11 | 12 | func NewHub(creds *Credentials, hubName string) Hub { 13 | return Hub{rpc: NewRPC(creds), hubName: hubName} 14 | } 15 | 16 | func (c Hub) CreateStream(args OptionalArguments) (stream Stream, err error) { 17 | data := map[string]interface{}{"hub": c.hubName} 18 | if args.Title != "" { 19 | data["title"] = args.Title 20 | } 21 | if args.PublishKey != "" { 22 | data["publishKey"] = args.PublishKey 23 | } 24 | if args.PublishSecurity != "" { 25 | data["publishSecurity"] = args.PublishSecurity 26 | } 27 | url := fmt.Sprintf("%s/streams", getApiBaseUrl()) 28 | err = c.rpc.PostCall(&stream, url, data) 29 | if err != nil { 30 | return 31 | } 32 | stream.rpc = c.rpc 33 | return 34 | } 35 | 36 | func (c Hub) GetStream(id string) (stream Stream, err error) { 37 | url := fmt.Sprintf("%s/streams/%s", getApiBaseUrl(), id) 38 | err = c.rpc.GetCall(&stream, url) 39 | if err != nil { 40 | return 41 | } 42 | stream.rpc = c.rpc 43 | return 44 | } 45 | 46 | func (c Hub) ListStreams(args OptionalArguments) (ret StreamList, err error) { 47 | url := fmt.Sprintf("%s/streams?hub=%s", getApiBaseUrl(), c.hubName) 48 | if args.Status != "" { 49 | url = fmt.Sprintf("%s&status=%s", url, args.Status) 50 | } 51 | if args.Marker != "" { 52 | url = fmt.Sprintf("%s&marker=%s", url, args.Marker) 53 | } 54 | if args.Limit > 0 { 55 | url = fmt.Sprintf("%s&limit=%d", url, args.Limit) 56 | } 57 | if args.Title != "" { 58 | url = fmt.Sprintf("%s&title=%s", url, args.Title) 59 | } 60 | resultWrapper := StreamList{} 61 | err = c.rpc.GetCall(&resultWrapper, url) 62 | if err != nil { 63 | return 64 | } 65 | count := len(resultWrapper.Items) 66 | streams := make([]*Stream, count) 67 | for i := 0; i < count; i++ { 68 | streams[i] = resultWrapper.Items[i] 69 | streams[i].rpc = c.rpc 70 | } 71 | ret.Items = streams 72 | ret.Marker = resultWrapper.Marker 73 | ret.End = resultWrapper.End 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /pili/model.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Stream struct { 8 | rpc *RPC 9 | Id string `json:"id"` 10 | CreatedAt time.Time `json:"createdAt"` 11 | UpdatedAt time.Time `json:"updatedAt"` 12 | Title string `json:"title"` 13 | Hub string `json:"hub"` 14 | Disabled bool `json:"disabled"` 15 | PublishKey string `json:"publishKey"` 16 | PublishSecurity string `json:"publishSecurity"` 17 | Profiles []string `json:"profiles,omitempty"` 18 | Hosts struct { 19 | Publish map[string]string `json:"publish,omitempty"` 20 | Live map[string]string `json:"live,omitempty"` 21 | Playback map[string]string `json:"playback,omitempty"` 22 | } `json:"hosts,omitempty"` 23 | } 24 | 25 | type StreamList struct { 26 | Marker string `json:"marker"` 27 | Items []*Stream `json:"items"` 28 | End bool `json:"end"` 29 | } 30 | 31 | type StreamId struct { 32 | Id string `json:"id"` 33 | } 34 | 35 | type StreamIdList struct { 36 | Marker string `json:"marker"` 37 | Items []*StreamId `json:"items"` 38 | } 39 | 40 | type StreamSegment struct { 41 | Start int64 `json:"start"` 42 | End int64 `json:"end"` 43 | } 44 | 45 | type StreamSegmentList struct { 46 | Start int64 `json:"start"` 47 | End int64 `json:"end"` 48 | Duration int64 `json:"duration"` 49 | Segments []*StreamSegment `json:"segments"` 50 | } 51 | 52 | type StreamStatus struct { 53 | Addr string `json:"addr"` 54 | StartFrom string `json:"startFrom"` 55 | Status string `json:"status"` 56 | BytesPerSecond float64 `json:"bytesPerSecond"` 57 | FramesPerSecond struct { 58 | Audio float64 `json:"audio"` 59 | Video float64 `json:"video"` 60 | Data float64 `json:"data"` 61 | } `json:"framesPerSecond"` 62 | } 63 | 64 | type StreamSaveAsResponse struct { 65 | Url string `json:"url"` 66 | TargetUrl string `json:"targetUrl"` 67 | PersistentId string `json:"persistentId"` 68 | } 69 | 70 | type StreamSnapshotResponse struct { 71 | TargetUrl string `json:"targetUrl"` 72 | PersistentId string `json:"persistentId"` 73 | } 74 | 75 | type OptionalArguments struct { 76 | Title string 77 | PublishKey string 78 | PublishSecurity string 79 | Disabled bool 80 | Hub string 81 | Idonly string 82 | Status string 83 | Marker string 84 | Limit uint 85 | Start int64 86 | End int64 87 | Time int64 88 | NotifyUrl string 89 | UserPipeline string 90 | } 91 | -------------------------------------------------------------------------------- /pili/rpc.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | func NewRPC(creds *Credentials) *RPC { 11 | t := NewTransport(creds, nil) 12 | tc := http.Client{Transport: t} 13 | return &RPC{&tc} 14 | } 15 | 16 | type RPC struct { 17 | *http.Client 18 | } 19 | 20 | func (r RPC) Do(req *http.Request) (resp *http.Response, err error) { 21 | req.Header.Set("User-Agent", UserAgent()) 22 | resp, err = r.Client.Do(req) 23 | if err != nil { 24 | return 25 | } 26 | return 27 | } 28 | 29 | func (r RPC) RequestWith( 30 | method string, 31 | url string, 32 | bodyType string, 33 | body io.Reader, 34 | bodyLength int) (resp *http.Response, err error) { 35 | 36 | req, err := http.NewRequest(method, url, body) 37 | if err != nil { 38 | return 39 | } 40 | req.Header.Set("Content-Type", bodyType) 41 | req.ContentLength = int64(bodyLength) 42 | return r.Do(req) 43 | } 44 | 45 | func (r RPC) Post(url string, data interface{}) (resp *http.Response, err error) { 46 | msg, err := json.Marshal(data) 47 | if err != nil { 48 | return 49 | } 50 | return r.RequestWith("POST", url, "application/json", bytes.NewReader(msg), len(msg)) 51 | } 52 | 53 | func (r RPC) Get(url string) (resp *http.Response, err error) { 54 | req, err := http.NewRequest("GET", url, nil) 55 | if err != nil { 56 | return 57 | } 58 | return r.Do(req) 59 | } 60 | 61 | func (r RPC) Del(url string) (resp *http.Response, err error) { 62 | req, err := http.NewRequest("DELETE", url, nil) 63 | if err != nil { 64 | return 65 | } 66 | return r.Do(req) 67 | } 68 | 69 | func (r RPC) PostCall(ret interface{}, url string, params interface{}) (err error) { 70 | resp, err := r.Post(url, params) 71 | if err != nil { 72 | return err 73 | } 74 | return callRet(ret, resp) 75 | } 76 | 77 | func (r RPC) GetCall(ret interface{}, url string) (err error) { 78 | resp, err := r.Get(url) 79 | if err != nil { 80 | return err 81 | } 82 | return callRet(ret, resp) 83 | } 84 | 85 | func (r RPC) DelCall(ret interface{}, url string) (err error) { 86 | resp, err := r.Del(url) 87 | if err != nil { 88 | return err 89 | } 90 | return callRet(ret, resp) 91 | } 92 | 93 | type ErrorInfo struct { 94 | Message string `json:"error"` 95 | ErrCode int `json:"errno"` 96 | Details map[string]error `json:"details,omitempty"` 97 | Code int `json:"code"` 98 | } 99 | 100 | func (r *ErrorInfo) Error() string { 101 | msg, _ := json.Marshal(r) 102 | return string(msg) 103 | } 104 | 105 | func ResponseError(resp *http.Response) (err error) { 106 | 107 | e := &ErrorInfo{ 108 | Code: resp.StatusCode, 109 | } 110 | 111 | if resp.StatusCode > 299 { 112 | if resp.ContentLength != 0 { 113 | if ct, ok := resp.Header["Content-Type"]; ok && ct[0] == "application/json" { 114 | json.NewDecoder(resp.Body).Decode(&e) 115 | } 116 | } 117 | } 118 | 119 | return e 120 | } 121 | 122 | func callRet(ret interface{}, resp *http.Response) (err error) { 123 | 124 | defer resp.Body.Close() 125 | 126 | if resp.StatusCode/100 == 2 { 127 | if ret != nil && resp.ContentLength != 0 { 128 | err = json.NewDecoder(resp.Body).Decode(ret) 129 | if err != nil { 130 | return 131 | } 132 | } 133 | return 134 | } 135 | return ResponseError(resp) 136 | } 137 | -------------------------------------------------------------------------------- /pili/stream.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "net/url" 10 | "time" 11 | ) 12 | 13 | func (s Stream) Refresh() (stream Stream, err error) { 14 | url := fmt.Sprintf("%s/streams/%s", getApiBaseUrl(), s.Id) 15 | err = s.rpc.GetCall(&stream, url) 16 | if err != nil { 17 | return 18 | } 19 | stream.rpc = s.rpc 20 | return 21 | } 22 | 23 | func (s Stream) ToJSONString() (jsonBlob string, err error) { 24 | jsonBytes, err := json.Marshal(s) 25 | jsonBlob = string(jsonBytes) 26 | return 27 | } 28 | 29 | func (s Stream) Enable() (stream Stream, err error) { 30 | data := map[string]bool{"disabled": false} 31 | url := fmt.Sprintf("%s/streams/%s", getApiBaseUrl(), s.Id) 32 | err = s.rpc.PostCall(&stream, url, data) 33 | stream.rpc = s.rpc 34 | return 35 | } 36 | 37 | func (s Stream) Disable() (stream Stream, err error) { 38 | data := map[string]bool{"disabled": true} 39 | url := fmt.Sprintf("%s/streams/%s", getApiBaseUrl(), s.Id) 40 | err = s.rpc.PostCall(&stream, url, data) 41 | stream.rpc = s.rpc 42 | return 43 | } 44 | 45 | type disabledArgs struct { 46 | Available string `json:"available"` 47 | DisabledTill int64 `json:"disabledTill"` 48 | } 49 | 50 | func (s Stream) DisableTill(till time.Time) error { 51 | args := &disabledArgs{ 52 | Available: "disabled", 53 | DisabledTill: till.Unix(), 54 | } 55 | url := fmt.Sprintf("%s/streams/%s/available", getApiBaseUrl(), s.Id) 56 | return s.rpc.PostCall(nil, url, args) 57 | } 58 | 59 | func (s Stream) Update() (stream Stream, err error) { 60 | data := map[string]interface{}{} 61 | if s.PublishKey != "" { 62 | data["publishKey"] = s.PublishKey 63 | } 64 | if s.PublishSecurity != "" { 65 | data["publishSecurity"] = s.PublishSecurity 66 | } 67 | url := fmt.Sprintf("%s/streams/%s", getApiBaseUrl(), s.Id) 68 | err = s.rpc.PostCall(&stream, url, data) 69 | stream.rpc = s.rpc 70 | return 71 | } 72 | 73 | func (s Stream) Delete() (ret interface{}, err error) { 74 | url := fmt.Sprintf("%s/streams/%s", getApiBaseUrl(), s.Id) 75 | err = s.rpc.DelCall(&ret, url) 76 | return 77 | } 78 | 79 | func (s Stream) Status() (ret StreamStatus, err error) { 80 | url := fmt.Sprintf("%s/streams/%s/status", getApiBaseUrl(), s.Id) 81 | err = s.rpc.GetCall(&ret, url) 82 | return 83 | } 84 | 85 | func (s Stream) Segments(args OptionalArguments) (ret StreamSegmentList, err error) { 86 | url := fmt.Sprintf("%s/streams/%s/segments", getApiBaseUrl(), s.Id) 87 | if args.Start > 0 { 88 | url = fmt.Sprintf("%s?start=%d", url, args.Start) 89 | } 90 | if args.End > 0 { 91 | url = fmt.Sprintf("%s&end=%d", url, args.End) 92 | } 93 | if args.Limit > 0 { 94 | url = fmt.Sprintf("%s&limit=%d", url, args.Limit) 95 | } 96 | err = s.rpc.GetCall(&ret, url) 97 | return 98 | } 99 | 100 | func (s Stream) SaveAs(name, format string, start, end int64, args OptionalArguments) (ret StreamSaveAsResponse, err error) { 101 | data := map[string]interface{}{"name": name, "start": start, "end": end} 102 | if args.NotifyUrl != "" { 103 | data["notifyUrl"] = args.NotifyUrl 104 | } 105 | if args.UserPipeline != "" { 106 | data["pipeline"] = args.UserPipeline 107 | } 108 | if format != "" { 109 | data["format"] = format 110 | } 111 | url := fmt.Sprintf("%s/streams/%s/saveas", getApiBaseUrl(), s.Id) 112 | fmt.Println("saveas url:", url, "data:", data) 113 | err = s.rpc.PostCall(&ret, url, data) 114 | return 115 | } 116 | 117 | func (s Stream) Snapshot(name, format string, args OptionalArguments) (ret StreamSnapshotResponse, err error) { 118 | data := map[string]interface{}{"name": name, "format": format} 119 | if args.Time > 0 { 120 | data["time"] = args.Time 121 | } 122 | if args.NotifyUrl != "" { 123 | data["notifyUrl"] = args.NotifyUrl 124 | } 125 | url := fmt.Sprintf("%s/streams/%s/snapshot", getApiBaseUrl(), s.Id) 126 | err = s.rpc.PostCall(&ret, url, data) 127 | return 128 | } 129 | 130 | // Publish URL 131 | // ------------------------------------------------------------------------------- 132 | func (s Stream) RtmpPublishUrl() (url string) { 133 | switch s.PublishSecurity { 134 | case "dynamic": 135 | url = s.rtmpPublishDynamicUrl() 136 | case "static": 137 | url = s.rtmpPublishStaticUrl() 138 | } 139 | return 140 | } 141 | 142 | func (s Stream) rtmpPublishDynamicUrl() (url string) { 143 | nonce := time.Now().UnixNano() 144 | url = fmt.Sprintf("%s?nonce=%d&token=%s", s.rtmpPublishBaseUrl(), nonce, s.publishToken(nonce)) 145 | return 146 | } 147 | 148 | func (s Stream) rtmpPublishStaticUrl() (url string) { 149 | url = fmt.Sprintf("%s?key=%s", s.rtmpPublishBaseUrl(), s.PublishKey) 150 | return 151 | } 152 | 153 | func (s Stream) rtmpPublishBaseUrl() (url string) { 154 | url = fmt.Sprintf("rtmp://%s/%s/%s", s.Hosts.Publish["rtmp"], s.Hub, s.Title) 155 | return 156 | } 157 | 158 | func (s Stream) publishToken(nonce int64) (token string) { 159 | u, _ := url.Parse(s.rtmpPublishBaseUrl()) 160 | uriStr := u.Path 161 | if u.RawQuery != "" { 162 | uriStr += "?" + u.RawQuery 163 | } 164 | uriStr = fmt.Sprintf("%s?nonce=%d", uriStr, nonce) 165 | token = s.sign([]byte(s.PublishKey), []byte(uriStr)) 166 | return 167 | } 168 | 169 | func (s Stream) sign(secret, data []byte) (token string) { 170 | h := hmac.New(sha1.New, secret) 171 | h.Write(data) 172 | token = base64.URLEncoding.EncodeToString(h.Sum(nil)) 173 | return 174 | } 175 | 176 | // RTMP Live Play URLs 177 | // -------------------------------------------------------------------------------- 178 | 179 | func (s Stream) RtmpLiveUrls() (urls map[string]string, err error) { 180 | urls = make(map[string]string) 181 | url := fmt.Sprintf("rtmp://%s/%s/%s", s.Hosts.Live["rtmp"], s.Hub, s.Title) 182 | urls[ORIGIN] = url 183 | return 184 | } 185 | 186 | // HLS Live Play URLs 187 | // -------------------------------------------------------------------------------- 188 | 189 | func (s Stream) HlsLiveUrls() (urls map[string]string, err error) { 190 | urls = make(map[string]string) 191 | urls[ORIGIN] = fmt.Sprintf("http://%s/%s/%s.m3u8", s.Hosts.Live["hls"], s.Hub, s.Title) 192 | return 193 | } 194 | 195 | // Http-Flv Live Play URLs 196 | // -------------------------------------------------------------------------------- 197 | 198 | func (s Stream) HttpFlvLiveUrls() (urls map[string]string, err error) { 199 | urls = make(map[string]string) 200 | urls[ORIGIN] = fmt.Sprintf("http://%s/%s/%s.flv", s.Hosts.Live["hdl"], s.Hub, s.Title) 201 | return 202 | } 203 | 204 | // HLS Playback URLs 205 | // -------------------------------------------------------------------------------- 206 | 207 | func (s Stream) HlsPlaybackUrls(start, end int64) (urls map[string]string, err error) { 208 | name := fmt.Sprintf("%d", time.Now().Unix()) 209 | ret, err := s.SaveAs(name, "", start, end, OptionalArguments{}) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | urls = make(map[string]string) 215 | urls[ORIGIN] = ret.Url 216 | return 217 | } 218 | -------------------------------------------------------------------------------- /pili/transport.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type Transport struct { 8 | creds Credentials 9 | transport http.RoundTripper 10 | } 11 | 12 | func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 13 | token, err := t.creds.MACToken(req) 14 | if err != nil { 15 | return 16 | } 17 | req.Header.Set("Authorization", "Qiniu "+token) 18 | return t.transport.RoundTrip(req) 19 | } 20 | 21 | func NewTransport(creds *Credentials, transport http.RoundTripper) *Transport { 22 | if transport == nil { 23 | transport = http.DefaultTransport 24 | } 25 | t := &Transport{transport: transport} 26 | t.creds = *creds 27 | return t 28 | } 29 | --------------------------------------------------------------------------------