瀏覽代碼

add api for livechannel

hangzws 7 年之前
父節點
當前提交
13639b9063
共有 11 個文件被更改,包括 1257 次插入7 次删除
  1. 30 0
      oss/auth.go
  2. 3 3
      oss/bucket_test.go
  3. 5 4
      oss/client_test.go
  4. 31 0
      oss/conn.go
  5. 25 0
      oss/conn_test.go
  6. 1 0
      oss/const.go
  7. 228 0
      oss/livechannel.go
  8. 393 0
      oss/livechannel_test.go
  9. 99 0
      oss/type.go
  10. 44 0
      sample/comm.go
  11. 398 0
      sample/livechannel.go

+ 30 - 0
oss/auth.go

@@ -5,10 +5,12 @@ import (
 	"crypto/hmac"
 	"crypto/sha1"
 	"encoding/base64"
+	"fmt"
 	"hash"
 	"io"
 	"net/http"
 	"sort"
+	"strconv"
 	"strings"
 )
 
@@ -61,6 +63,34 @@ func (conn Conn) getSignedStr(req *http.Request, canonicalizedResource string) s
 	return signedStr
 }
 
+func (conn Conn) getRtmpSignedStr(bucketName, channelName, playlistName string, expiration int64, params map[string]interface{}) string {
+	if params[HTTPParamAccessKeyID] == nil {
+		return ""
+	}
+
+	canonResource := fmt.Sprintf("/%s/%s", bucketName, channelName)
+	canonParamsKeys := []string{}
+	for key := range params {
+		if key != HTTPParamAccessKeyID && key != HTTPParamSignature && key != HTTPParamExpires && key != HTTPParamSecurityToken {
+			canonParamsKeys = append(canonParamsKeys, key)
+		}
+	}
+
+	sort.Strings(canonParamsKeys)
+	canonParamsStr := ""
+	for _, key := range canonParamsKeys {
+		canonParamsStr = fmt.Sprintf("%s%s:%s\n", canonParamsStr, key, params[key].(string))
+	}
+
+	expireStr := strconv.FormatInt(expiration, 10)
+	signStr := expireStr + "\n" + canonParamsStr + canonResource
+
+	h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(conn.config.AccessKeySecret))
+	io.WriteString(h, signStr)
+	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
+	return signedStr
+}
+
 // Additional function for function SignHeader.
 func newHeaderSorter(m map[string]string) *headerSorter {
 	hs := &headerSorter{

+ 3 - 3
oss/bucket_test.go

@@ -58,7 +58,7 @@ func (s *OssBucketSuite) SetUpSuite(c *C) {
 	testLogger.Println("test bucket started")
 }
 
-// Run before each test or benchmark starts running
+// Run once after all tests or benckmarks have finished running
 func (s *OssBucketSuite) TearDownSuite(c *C) {
 	for _, bucket := range []*Bucket{s.bucket, s.archiveBucket} {
 		// Delete Multipart
@@ -84,13 +84,13 @@ func (s *OssBucketSuite) TearDownSuite(c *C) {
 	testLogger.Println("test bucket completed")
 }
 
-// Run after each test or benchmark runs
+// Run before each test or benchmark starts
 func (s *OssBucketSuite) SetUpTest(c *C) {
 	err := removeTempFiles("../oss", ".jpg")
 	c.Assert(err, IsNil)
 }
 
-// Run once after all tests or benchmarks have finished running
+// Run after each test or benchmark runs
 func (s *OssBucketSuite) TearDownTest(c *C) {
 	err := removeTempFiles("../oss", ".jpg")
 	c.Assert(err, IsNil)

+ 5 - 4
oss/client_test.go

@@ -43,12 +43,12 @@ var (
 
 const (
 	// prefix of bucket name for bucket ops test
-	bucketNamePrefix = "go-sdk-test-bucket-xyz-"
+	bucketNamePrefix = "go-sdk-test-bucket-abc-"
 	// bucket name for object ops test
-	bucketName        = "go-sdk-test-bucket-xyz-for-object"
-	archiveBucketName = "go-sdk-test-bucket-xyz-for-archive"
+	bucketName        = "go-sdk-test-bucket-abc-for-object"
+	archiveBucketName = "go-sdk-test-bucket-abc-for-archive"
 	// object name for object ops test
-	objectNamePrefix = "go-sdk-test-object-xyz-"
+	objectNamePrefix = "go-sdk-test-object-abc-"
 	// sts region is one and only hangzhou
 	stsRegion = "cn-hangzhou"
 )
@@ -174,6 +174,7 @@ func (s *OssClientSuite) TestCreateBucket(c *C) {
 
 	err = client.DeleteBucket(bucketNameTest)
 	c.Assert(err, IsNil)
+	time.Sleep(5 * time.Second)
 
 	// Create with ACLPublicRead
 	err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead))

+ 31 - 0
oss/conn.go

@@ -281,6 +281,27 @@ func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expir
 	return conn.url.getSignURL(bucketName, objectName, urlParams)
 }
 
+func (conn Conn) signRtmpURL(bucketName, channelName, playlistName string, expiration int64) string {
+	params := map[string]interface{}{}
+	if playlistName != "" {
+		params[HTTPParamPlaylistName] = playlistName
+	}
+	expireStr := strconv.FormatInt(expiration, 10)
+	params[HTTPParamExpires] = expireStr
+
+	if conn.config.AccessKeyID != "" {
+		params[HTTPParamAccessKeyID] = conn.config.AccessKeyID
+		if conn.config.SecurityToken != "" {
+			params[HTTPParamSecurityToken] = conn.config.SecurityToken
+		}
+		signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, params)
+		params[HTTPParamSignature] = signedStr
+	}
+
+	urlParams := conn.getURLParams(params)
+	return conn.url.getSignRtmpURL(bucketName, channelName, urlParams)
+}
+
 // handle request body
 func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64,
 	listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) {
@@ -555,6 +576,16 @@ func (um urlMaker) getSignURL(bucket, object, params string) string {
 	return fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params)
 }
 
+// Build Sign Rtmp URL
+func (um urlMaker) getSignRtmpURL(bucket, channelName, params string) string {
+	host, path := um.buildURL(bucket, "live")
+
+	channelName = url.QueryEscape(channelName)
+	channelName = strings.Replace(channelName, "+", "%20", -1)
+
+	return fmt.Sprintf("rtmp://%s%s/%s?%s", host, path, channelName, params)
+}
+
 // Build URL
 func (um urlMaker) buildURL(bucket, object string) (string, string) {
 	var host = ""

+ 25 - 0
oss/conn_test.go

@@ -2,6 +2,8 @@ package oss
 
 import (
 	"net/http"
+	"strings"
+	"time"
 
 	. "gopkg.in/check.v1"
 )
@@ -124,3 +126,26 @@ func (s *OssConnSuite) TestConnToolFunc(c *C) {
 	c.Assert(len(unexpect.Error()) > 0, Equals, true)
 	c.Assert(unexpect.Got(), Equals, 202)
 }
+
+func (s *OssConnSuite) TestSignRtmpURL(c *C) {
+	cfg := getDefaultOssConfig()
+	um := urlMaker{}
+	um.Init(endpoint, false, false)
+	conn := Conn{cfg, &um, nil}
+
+	//Anonymous
+	channelName := "test-sign-rtmp-url"
+	playlistName := "playlist.m3u8"
+	expiration := time.Now().Unix() + 3600
+	signedRtmpURL := conn.signRtmpURL(bucketName, channelName, playlistName, expiration)
+	playURL := getPublishURL(bucketName, channelName)
+	hasPrefix := strings.HasPrefix(signedRtmpURL, playURL)
+	c.Assert(hasPrefix, Equals, true)
+
+	//empty playlist name
+	playlistName = ""
+	signedRtmpURL = conn.signRtmpURL(bucketName, channelName, playlistName, expiration)
+	playURL = getPublishURL(bucketName, channelName)
+	hasPrefix = strings.HasPrefix(signedRtmpURL, playURL)
+	c.Assert(hasPrefix, Equals, true)
+}

+ 1 - 0
oss/const.go

@@ -114,6 +114,7 @@ const (
 	HTTPParamAccessKeyID   = "OSSAccessKeyId"
 	HTTPParamSignature     = "Signature"
 	HTTPParamSecurityToken = "security-token"
+	HTTPParamPlaylistName  = "playlistName"
 )
 
 // 其它常量

+ 228 - 0
oss/livechannel.go

@@ -0,0 +1,228 @@
+package oss
+
+import (
+	"bytes"
+	"encoding/xml"
+	"fmt"
+	"net/http"
+	"strconv"
+	"time"
+)
+
+//
+// CreateLiveChannel 创建推流直播频道
+//
+// channelName 直播流频道名称
+// config 直播流频的配置信息
+//
+// CreateLiveChannelResult 创建直播流频请求的返回结果
+// error 操作无错误时返回nil,非nil为错误信息
+//
+func (bucket Bucket) CreateLiveChannel(channelName string, config LiveChannelConfiguration) (CreateLiveChannelResult, error) {
+	var out CreateLiveChannelResult
+
+	bs, err := xml.Marshal(config)
+	if err != nil {
+		return out, err
+	}
+
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	params := map[string]interface{}{}
+	params["live"] = nil
+	resp, err := bucket.do("PUT", channelName, params, nil, buffer, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	return out, err
+}
+
+//
+// PutLiveChannelStatus 设置直播频道的状态,有两种状态可选:enabled和disabled
+//
+// channelName 直播流频道的名称
+// status 状态,有两种状态可选:enabled和disabled
+//
+// error 操作无错误时返回nil, 非nil为错误信息
+//
+func (bucket Bucket) PutLiveChannelStatus(channelName, status string) error {
+	params := map[string]interface{}{}
+	params["live"] = nil
+	params["status"] = status
+
+	resp, err := bucket.do("PUT", channelName, params, nil, nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	return checkRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// PostVodPlaylist 根据指定的playlist name以及startTime和endTime生成一个点播的播放列表
+//
+// channelName 直播流频道的名称
+// playlistName 指定生成的点播列表的名称,必须以”.m3u8“结尾
+// startTime 指定查询ts文件的起始时间
+// endTime 指定查询ts文件的终止时间
+//
+// error 操作无错误是返回nil, 非nil为错误信息
+//
+func (bucket Bucket) PostVodPlaylist(channelName, playlistName string, startTime, endTime time.Time) error {
+	params := map[string]interface{}{}
+	params["vod"] = nil
+	params["startTime"] = strconv.FormatInt(startTime.Unix(), 10)
+	params["endTime"] = strconv.FormatInt(endTime.Unix(), 10)
+
+	key := fmt.Sprintf("%s/%s", channelName, playlistName)
+	resp, err := bucket.do("POST", key, params, nil, nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	return checkRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+//
+// GetLiveChannelStat 获取指定直播流频道当前推流的状态
+//
+// channelName 直播流频道的名称
+//
+// LiveChannelStat 直播流频道当前推流状态信息
+// error 操作无错误是返回nil, 非nil为错误信息
+//
+func (bucket Bucket) GetLiveChannelStat(channelName string) (LiveChannelStat, error) {
+	var out LiveChannelStat
+	params := map[string]interface{}{}
+	params["live"] = nil
+	params["comp"] = "stat"
+
+	resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	return out, err
+}
+
+//
+// GetLiveChannelInfo 获取直播流频道的配置信息
+//
+// channelName 直播流频道的名称
+//
+// LiveChannelConfiguration 直播流频道的配置信息
+// error 操作无错误返回nil, 非nil为错误信息
+//
+func (bucket Bucket) GetLiveChannelInfo(channelName string) (LiveChannelConfiguration, error) {
+	var out LiveChannelConfiguration
+	params := map[string]interface{}{}
+	params["live"] = nil
+
+	resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	return out, err
+}
+
+//
+// GetLiveChannelHistory 获取直播流频道的历史推流记录
+//
+// channelName 直播流频道名称
+//
+// LiveChannelHistory 返回的直播流历史推流记录
+// error 操作无错误返回nil, 非nil为错误信息
+//
+func (bucket Bucket) GetLiveChannelHistory(channelName string) (LiveChannelHistory, error) {
+	var out LiveChannelHistory
+	params := map[string]interface{}{}
+	params["live"] = nil
+	params["comp"] = "history"
+
+	resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	return out, err
+}
+
+//
+// ListLiveChannel 获取直播流频道的信息列表
+//
+// options  筛选选项:Prefix指定的前缀、MaxKeys为返回的最大数目、Marker代表从哪个livechannel作为游标开始列表
+//
+// ListLiveChannelResult 返回的livechannel列表结果
+// error 操作无结果返回nil, 非nil为错误信息
+//
+func (bucket Bucket) ListLiveChannel(options ...Option) (ListLiveChannelResult, error) {
+	var out ListLiveChannelResult
+
+	//options = append(options, EncodingType("url"))
+	params, err := getRawParams(options)
+	if err != nil {
+		return out, err
+	}
+
+	params["live"] = nil
+
+	resp, err := bucket.do("GET", "", params, nil, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	return out, err
+}
+
+//
+// DeleteLiveChannel 删除指定的livechannel,当有客户端正在想livechannel推流时,删除请求回失败,本接口志辉删除livechannel本身,不会删除推流生成的文件
+//
+// channelName 直播流的频道名称
+//
+// error 操作无错误返回nil, 非nil为错误信息
+//
+func (bucket Bucket) DeleteLiveChannel(channelName string) error {
+	params := map[string]interface{}{}
+	params["live"] = nil
+
+	resp, err := bucket.do("DELETE", channelName, params, nil, nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
+}
+
+//
+// SignRtmpURL 生成RTMP推流签名的URL, 常见的用法是生成加签的URL以供授信用户向OSS推RTMP流。
+//
+// channelName 直播流的频道名称
+// playlistName 播放列表名称,其值会覆盖LiveChannel中的配置,必须以“.m3u8”结尾
+// expires 过期时间(单位:秒),链接在当前时间再过expires秒后过期
+//
+// string 返回的加签的rtmp推流地址
+// error 操作无错误返回nil, 非nil为错误信息
+//
+func (bucket Bucket) SignRtmpURL(channelName, playlistName string, expires int64) (string, error) {
+	if expires < 0 {
+		return "", fmt.Errorf("invalid argument: %d, expires must greater than 0", expires)
+	}
+	expiration := time.Now().Unix() + expires
+
+	return bucket.Client.Conn.signRtmpURL(bucket.BucketName, channelName, playlistName, expiration), nil
+}

+ 393 - 0
oss/livechannel_test.go

@@ -0,0 +1,393 @@
+package oss
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssBucketLiveChannelSuite struct {
+	client *Client
+	bucket *Bucket
+}
+
+var _ = Suite(&OssBucketLiveChannelSuite{})
+
+// Run once when the suite starts running
+func (s *OssBucketLiveChannelSuite) SetUpSuite(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+	s.client = client
+
+	err = s.client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+	time.Sleep(5 * time.Second)
+
+	bucket, err := s.client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+	s.bucket = bucket
+
+	testLogger.Println("test livechannel started...")
+}
+
+// Run once after all tests or benckmarks have finished running
+func (s *OssBucketLiveChannelSuite) TearDownSuite(c *C) {
+	marker := ""
+	for {
+		result, err := s.bucket.ListLiveChannel(Marker(marker))
+		c.Assert(err, IsNil)
+
+		for _, channel := range result.LiveChannel {
+			err := s.bucket.DeleteLiveChannel(channel.Name)
+			c.Assert(err, IsNil)
+		}
+
+		if result.IsTruncated {
+			marker = result.NextMarker
+		} else {
+			break
+		}
+	}
+
+	testLogger.Println("test livechannel done...")
+}
+
+// Run after each test or benchmark runs
+func (s *OssBucketLiveChannelSuite) SetUpTest(c *C) {
+
+}
+
+// Run once after all tests or benchmarks have finished running
+func (s *OssBucketLiveChannelSuite) TearDownTest(c *C) {
+
+}
+
+// TestCreateLiveChannel
+func (s *OssBucketLiveChannelSuite) TestCreateLiveChannel(c *C) {
+	channelName := "test-create-channel"
+	playlistName := "test-create-channel.m3u8"
+
+	target := LiveChannelTarget{
+		PlaylistName: playlistName,
+		Type:         "HLS",
+	}
+
+	config := LiveChannelConfiguration{
+		Description: "livechannel for test",
+		Status:      "enabled",
+		Target:      target,
+	}
+
+	result, err := s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+
+	playURL := getPlayURL(bucketName, channelName, playlistName)
+	publishURL := getPublishURL(bucketName, channelName)
+	c.Assert(result.PlayUrls[0], Equals, playURL)
+	c.Assert(result.PublishUrls[0], Equals, publishURL)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+
+	invalidType := randStr(4)
+	invalidTarget := LiveChannelTarget{
+		PlaylistName: playlistName,
+		Type:         invalidType,
+	}
+
+	invalidConfig := LiveChannelConfiguration{
+		Description: "livechannel for test",
+		Status:      "enabled",
+		Target:      invalidTarget,
+	}
+
+	_, err = s.bucket.CreateLiveChannel(channelName, invalidConfig)
+	c.Assert(err, NotNil)
+}
+
+// TestDeleteLiveChannel
+func (s *OssBucketLiveChannelSuite) TestDeleteLiveChannel(c *C) {
+	channelName := "test-delete-channel"
+
+	target := LiveChannelTarget{
+		Type: "HLS",
+	}
+
+	config := LiveChannelConfiguration{
+		Target: target,
+	}
+
+	_, err := s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+
+	config, err = s.bucket.GetLiveChannelInfo(channelName)
+	c.Assert(err, NotNil)
+}
+
+// TestGetLiveChannelInfo
+func (s *OssBucketLiveChannelSuite) TestGetLiveChannelInfo(c *C) {
+	channelName := "test-get-channel-status"
+
+	_, err := s.bucket.GetLiveChannelInfo(channelName)
+	c.Assert(err, NotNil)
+
+	createCfg := LiveChannelConfiguration{
+		Target: LiveChannelTarget{
+			Type:         "HLS",
+			FragDuration: 10,
+			FragCount:    4,
+			PlaylistName: "test-get-channel-status.m3u8",
+		},
+	}
+
+	_, err = s.bucket.CreateLiveChannel(channelName, createCfg)
+	c.Assert(err, IsNil)
+
+	getCfg, err := s.bucket.GetLiveChannelInfo(channelName)
+	c.Assert(err, IsNil)
+	c.Assert("enabled", Equals, getCfg.Status) //默认值为enabled
+	c.Assert("HLS", Equals, getCfg.Target.Type)
+	c.Assert(10, Equals, getCfg.Target.FragDuration)
+	c.Assert(4, Equals, getCfg.Target.FragCount)
+	c.Assert("test-get-channel-status.m3u8", Equals, getCfg.Target.PlaylistName)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+}
+
+// TestPutLiveChannelStatus
+func (s *OssBucketLiveChannelSuite) TestPutLiveChannelStatus(c *C) {
+	channelName := "test-put-channel-status"
+
+	config := LiveChannelConfiguration{
+		Status: "disabled",
+		Target: LiveChannelTarget{
+			Type: "HLS",
+		},
+	}
+
+	_, err := s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+	getCfg, err := s.bucket.GetLiveChannelInfo(channelName)
+	c.Assert(err, IsNil)
+	c.Assert("disabled", Equals, getCfg.Status)
+
+	err = s.bucket.PutLiveChannelStatus(channelName, "enabled")
+	c.Assert(err, IsNil)
+	getCfg, err = s.bucket.GetLiveChannelInfo(channelName)
+	c.Assert(err, IsNil)
+	c.Assert("enabled", Equals, getCfg.Status)
+
+	err = s.bucket.PutLiveChannelStatus(channelName, "disabled")
+	c.Assert(err, IsNil)
+	getCfg, err = s.bucket.GetLiveChannelInfo(channelName)
+	c.Assert(err, IsNil)
+	c.Assert("disabled", Equals, getCfg.Status)
+
+	invalidStatus := randLowStr(9)
+	err = s.bucket.PutLiveChannelStatus(channelName, invalidStatus)
+	c.Assert(err, NotNil)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+}
+
+// TestGetLiveChannelHistory()
+func (s *OssBucketLiveChannelSuite) TestGetLiveChannelHistory(c *C) {
+	channelName := "test-get-channel-history"
+
+	_, err := s.bucket.GetLiveChannelHistory(channelName)
+	c.Assert(err, NotNil)
+
+	config := LiveChannelConfiguration{
+		Target: LiveChannelTarget{
+			Type: "HLS",
+		},
+	}
+
+	_, err = s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+
+	history, err := s.bucket.GetLiveChannelHistory(channelName)
+	c.Assert(err, IsNil)
+	c.Assert(len(history.Record), Equals, 0)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+}
+
+// TestGetLiveChannelStat
+func (s *OssBucketLiveChannelSuite) TestGetLiveChannelStat(c *C) {
+	channelName := "test-get-channel-stat"
+
+	_, err := s.bucket.GetLiveChannelStat(channelName)
+	c.Assert(err, NotNil)
+
+	config := LiveChannelConfiguration{
+		Target: LiveChannelTarget{
+			Type: "HLS",
+		},
+	}
+
+	_, err = s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+
+	stat, err := s.bucket.GetLiveChannelStat(channelName)
+	c.Assert(err, IsNil)
+	c.Assert(stat.Status, Equals, "Idle")
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+}
+
+// TestPostVodPlaylist
+func (s *OssBucketLiveChannelSuite) TestPostVodPlaylist(c *C) {
+	channelName := "test-post-vod-playlist"
+	playlistName := "test-post-vod-playlist.m3u8"
+
+	config := LiveChannelConfiguration{
+		Target: LiveChannelTarget{
+			Type: "HLS",
+		},
+	}
+
+	_, err := s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+
+	endTime := time.Now().Add(-1 * time.Minute)
+	startTime := endTime.Add(-60 * time.Minute)
+
+	err = s.bucket.PostVodPlaylist(channelName, playlistName, startTime, endTime)
+	c.Assert(err, NotNil)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+}
+
+// TestListLiveChannel
+func (s *OssBucketLiveChannelSuite) TestListLiveChannel(c *C) {
+	result, err := s.bucket.ListLiveChannel()
+	c.Assert(err, IsNil)
+	ok := compareListResult(result, "", "", "", 100, false, 0)
+	c.Assert(ok, Equals, true)
+
+	prefix := "test-list-channel"
+	for i := 0; i < 200; i++ {
+		channelName := fmt.Sprintf("%s-%03d", prefix, i)
+
+		config := LiveChannelConfiguration{
+			Target: LiveChannelTarget{
+				Type: "HLS",
+			},
+		}
+
+		_, err := s.bucket.CreateLiveChannel(channelName, config)
+		c.Assert(err, IsNil)
+	}
+
+	result, err = s.bucket.ListLiveChannel()
+	c.Assert(err, IsNil)
+	nextMarker := fmt.Sprintf("%s-099", prefix)
+	ok = compareListResult(result, "", "", nextMarker, 100, true, 100)
+	c.Assert(ok, Equals, true)
+
+	randPrefix := randStr(5)
+	result, err = s.bucket.ListLiveChannel(Prefix(randPrefix))
+	c.Assert(err, IsNil)
+	ok = compareListResult(result, randPrefix, "", "", 100, false, 0)
+	c.Assert(ok, Equals, true)
+
+	marker := fmt.Sprintf("%s-100", prefix)
+	result, err = s.bucket.ListLiveChannel(Prefix(prefix), Marker(marker))
+	c.Assert(err, IsNil)
+	ok = compareListResult(result, prefix, marker, "", 100, false, 99)
+	c.Assert(ok, Equals, true)
+
+	maxKeys := 1000
+	result, err = s.bucket.ListLiveChannel(MaxKeys(maxKeys))
+	c.Assert(err, IsNil)
+	ok = compareListResult(result, "", "", "", maxKeys, false, 200)
+
+	for i := 0; i < 200; i++ {
+		channelName := fmt.Sprintf("%s-%03d", prefix, i)
+		err := s.bucket.DeleteLiveChannel(channelName)
+		c.Assert(err, IsNil)
+	}
+}
+
+// TestPostVodPlaylist
+func (s *OssBucketLiveChannelSuite) TestSignRtmpURL(c *C) {
+	channelName := "test-sign-rtmp-url"
+	playlistName := "test-sign-rtmp-url.m3u8"
+
+	config := LiveChannelConfiguration{
+		Target: LiveChannelTarget{
+			Type:         "HLS",
+			PlaylistName: playlistName,
+		},
+	}
+
+	_, err := s.bucket.CreateLiveChannel(channelName, config)
+	c.Assert(err, IsNil)
+
+	expires := int64(3600)
+	signedRtmpURL, err := s.bucket.SignRtmpURL(channelName, playlistName, expires)
+	c.Assert(err, IsNil)
+	playURL := getPublishURL(s.bucket.BucketName, channelName)
+	hasPrefix := strings.HasPrefix(signedRtmpURL, playURL)
+	c.Assert(hasPrefix, Equals, true)
+
+	invalidExpires := int64(-1)
+	signedRtmpURL, err = s.bucket.SignRtmpURL(channelName, playlistName, invalidExpires)
+	c.Assert(err, NotNil)
+
+	err = s.bucket.DeleteLiveChannel(channelName)
+	c.Assert(err, IsNil)
+}
+
+// private
+// getPlayURL - 获取直播流频道的播放地址
+func getPlayURL(bucketName, channelName, playlistName string) string {
+	host := ""
+	useHTTPS := false
+	if strings.Contains(endpoint, "https://") {
+		host = endpoint[8:]
+		useHTTPS = true
+	} else if strings.Contains(endpoint, "http://") {
+		host = endpoint[7:]
+	} else {
+		host = endpoint
+	}
+
+	if useHTTPS {
+		return fmt.Sprintf("https://%s.%s/%s/%s", bucketName, host, channelName, playlistName)
+	}
+	return fmt.Sprintf("http://%s.%s/%s/%s", bucketName, host, channelName, playlistName)
+}
+
+// getPublistURL - 获取直播流频道的推流地址
+func getPublishURL(bucketName, channelName string) string {
+	host := ""
+	if strings.Contains(endpoint, "https://") {
+		host = endpoint[8:]
+	} else if strings.Contains(endpoint, "http://") {
+		host = endpoint[7:]
+	} else {
+		host = endpoint
+	}
+
+	return fmt.Sprintf("rtmp://%s.%s/live/%s", bucketName, host, channelName)
+}
+
+func compareListResult(result ListLiveChannelResult, prefix, marker, nextMarker string, maxKey int, isTruncated bool, count int) bool {
+	if result.Prefix != prefix || result.Marker != marker || result.NextMarker != nextMarker || result.MaxKeys != maxKey || result.IsTruncated != isTruncated || len(result.LiveChannel) != count {
+		return false
+	}
+
+	return true
+}

+ 99 - 0
oss/type.go

@@ -448,3 +448,102 @@ type createBucketConfiguration struct {
 	XMLName      xml.Name         `xml:"CreateBucketConfiguration"`
 	StorageClass StorageClassType `xml:"StorageClass,omitempty"`
 }
+
+// LiveChannelConfiguration livechannel的配置信息
+type LiveChannelConfiguration struct {
+	XMLName     xml.Name          `xml:"LiveChannelConfiguration"`
+	Description string            `xml:"Description,omitempty"` //livechannel的描述信息,最长128字节
+	Status      string            `xml:"Status,omitempty"`      //指定livechannel的状态
+	Target      LiveChannelTarget `xml:"Target"`                //保存转储配置的容器
+	//Snapshot    LiveChannelSnapshot `xml:"Snapshot,omitempty"` //保存高频截图操作Snapshot选项的容器
+}
+
+// LiveChannelTarget livechannel的转储配置信息
+type LiveChannelTarget struct {
+	XMLName      xml.Name `xml:"Target"`
+	Type         string   `xml:"Type"`                   //指定转储的类型,只支持HLS
+	FragDuration int      `xml:"FragDuration,omitempty"` //当Type为HLS时,指定每个ts文件的时长(单位:秒),取值范围为【1,100】的整数
+	FragCount    int      `xml:"FragCount,omitempty"`    //当Type为HLS时,指定m3u8文件中包含ts文件的个数,取值范围为【1,100】的整数
+	PlaylistName string   `xml:"PlaylistName,omitempty"` //当Type为HLS时,指定生成m3u8文件的名称,必须以“.m3u8”结尾,长度范围为【6,128】
+}
+
+/*
+// LiveChannelSnapshot livechannel关于高频截图操作snapshot的配置信息
+type LiveChannelSnapshot struct {
+	XMLName     xml.Name `xml:"Snapshot"`
+	RoleName    string   `xml:"RoleName,omitempty"`    //高频截图操作的角色名称,要求有DestBucket的写权限和向NotifyTopic发消息的权限
+	DestBucket  string   `xml:"DestBucket,omitempty"`  //保存高频截图的目标Bucket,要求与当前Bucket是同一个Owner
+	NotifyTopic string   `xml:"NotifyTopic,omitempty"` //用于通知用户高频截图操作结果的MNS的Topic
+	Interval    int      `xml:"Interval,omitempty"`    //高频截图的间隔长度,单位为s,如果该段间隔时间内没有关键帧(I帧),那么该间隔时间不截图
+}
+*/
+
+// CreateLiveChannelResult 创建livechannel请求的返回结果
+type CreateLiveChannelResult struct {
+	XMLName     xml.Name `xml:"CreateLiveChannelResult"`
+	PublishUrls []string `xml:"PublishUrls>Url"` //推流地址列表
+	PlayUrls    []string `xml:"PlayUrls>Url"`    //播放地址的列表
+}
+
+// LiveChannelStat 获取livechannel状态请求的返回结果
+type LiveChannelStat struct {
+	XMLName       xml.Name         `xml:"LiveChannelStat"`
+	Status        string           `xml:"Status"`        //livechannel当前推流的状态,Disabled, Live, Idle
+	ConnectedTime time.Time        `xml:"ConnectedTime"` //当Status为Live时,表示当前客户开始推流的时间,使用ISO8601格式表示
+	RemoteAddr    string           `xml:"RemoteAddr"`    //当Status为Live时,表示当前推流客户端的ip地址
+	Video         LiveChannelVideo `xml:"Video"`         //当Status为Live时,表示视频流的信息
+	Audio         LiveChannelAudio `xml:"Audio"`         //当Status为Live时, 表示音频流的信息
+}
+
+// LiveChannelVideo 当livechannel的状态为live时,livechannel视频流的信息
+type LiveChannelVideo struct {
+	XMLName   xml.Name `xml:"Video"`
+	Width     int      `xml:"Width"`     //视频流的画面宽度(单位:像素)
+	Height    int      `xml:"Height"`    //视频流的画面高度(单位:像素)
+	FrameRate int      `xml:"FrameRate"` //视频流的帧率
+	Bandwidth int      `xml:"Bandwidth"` //视频流的码率(单位:B/s)
+}
+
+// LiveChannelAudio 当livechannel的状态为live时,livechannel音频流的信息
+type LiveChannelAudio struct {
+	XMLName    xml.Name `xml:"Audio"`
+	SampleRate int      `xml:"SampleRate"` //音频流的采样率
+	Bandwidth  int      `xml:"Bandwidth"`  //音频流的码率(单位:B/s)
+	Codec      string   `xml:"Codec"`      //音频流的编码格式
+}
+
+// LiveChannelHistory - livechannel的历史所有推流记录,目前最多会返回指定livechannel最近10次的推流记录
+type LiveChannelHistory struct {
+	XMLName xml.Name     `xml:"LiveChannelHistory"`
+	Record  []LiveRecord `xml:"LiveRecord"` //单个推流记录
+}
+
+// LiveRecord - 单个推流记录
+type LiveRecord struct {
+	XMLName    xml.Name  `xml:"LiveRecord"`
+	StartTime  time.Time `xml:"StartTime"`  //推流开始时间,使用ISO8601格式表示
+	EndTime    time.Time `xml:"EndTime"`    //推流结束时间,使用ISO8601格式表示
+	RemoteAddr string    `xml:"RemoteAddr"` //推流客户端的ip地址
+}
+
+// ListLiveChannelResult -
+type ListLiveChannelResult struct {
+	XMLName     xml.Name          `xml:"ListLiveChannelResult"`
+	Prefix      string            `xml:"Prefix"`      //返回以prifix作为前缀的livechannel,注意使用prifix查询时,返回的key中仍会包含prifix
+	Marker      string            `xml:"Marker"`      //以marker之后按字母排序的第一个livechanel开始返回
+	MaxKeys     int               `xml:"MaxKeys"`     //返回livechannel的最大数,如果不设定,默认为100,max-key的取值不能大于1000
+	IsTruncated bool              `xml:"IsTruncated"` //指明是否所有的结果都已经返回,“true”表示本次没有返回全部结果,“false”则表示已经返回了全部结果
+	NextMarker  string            `xml:"NextMarker"`  //如果本次没有返回全部结果,NextMarker用于表明下一请求的Marker值
+	LiveChannel []LiveChannelInfo `xml:"LiveChannel"` //livechannel的基本信息
+}
+
+// LiveChannelInfo -
+type LiveChannelInfo struct {
+	XMLName      xml.Name  `xml:"LiveChannel"`
+	Name         string    `xml:"Name"`            //livechannel的名称
+	Description  string    `xml:"Description"`     //livechannel的描述信息
+	Status       string    `xml:"Status"`          //livechannel的状态,有效值:disabled, enabled
+	LastModified time.Time `xml:"LastModified"`    //livechannel的最后修改时间,使用ISO8601格式表示
+	PublishUrls  []string  `xml:"PublishUrls>Url"` //推流地址列表
+	PlayUrls     []string  `xml:"PlayUrls>Url"`    //播放地址列表
+}

+ 44 - 0
sample/comm.go

@@ -43,6 +43,50 @@ func GetTestBucket(bucketName string) (*oss.Bucket, error) {
 	return bucket, nil
 }
 
+// DeleteTestBucketAndLiveChannel 删除sample的channelname和bucket,该函数为了简化sample,让sample代码更明了
+func DeleteTestBucketAndLiveChannel(bucketName string) error {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		return err
+	}
+
+	// Get Bucket
+	bucket, err := client.Bucket(bucketName)
+	if err != nil {
+		return err
+	}
+
+	marker := ""
+	for {
+		result, err := bucket.ListLiveChannel(oss.Marker(marker))
+		if err != nil {
+			HandleError(err)
+		}
+
+		for _, channel := range result.LiveChannel {
+			err := bucket.DeleteLiveChannel(channel.Name)
+			if err != nil {
+				HandleError(err)
+			}
+		}
+
+		if result.IsTruncated {
+			marker = result.NextMarker
+		} else {
+			break
+		}
+	}
+
+	// Delete Bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // DeleteTestBucketAndObject 删除sample的object和bucket,该函数为了简化sample,让sample代码更明了
 func DeleteTestBucketAndObject(bucketName string) error {
 	// New Client

+ 398 - 0
sample/livechannel.go

@@ -0,0 +1,398 @@
+package sample
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CreateLiveChannelSample - 创建livechannel的sample
+func CreateLiveChannelSample() {
+	channelName := "create-livechannel"
+	//创建bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1 - 完整配置livechannel创建
+	config := oss.LiveChannelConfiguration{
+		Description: "sample-for-livechannel", //livechannel的描述信息,最长128字节,非必须
+		Status:      "enabled",                //指定livechannel的状态,非必须(默认值:"enabled", 值只能是"enabled", "disabled")
+		Target: oss.LiveChannelTarget{
+			Type:         "HLS",                          //指定转储的类型,只支持HLS, 必须
+			FragDuration: 10,                             //指定每个ts文件的时长(单位:秒),取值范围为【1,100】的整数, 非必须(默认值:5)
+			FragCount:    4,                              //指定m3u8文件中包含ts文件的个数,取值范围为【1,100】的整数,非必须(默认值:3)
+			PlaylistName: "test-get-channel-status.m3u8", //当Type为HLS时,指定生成m3u8文件的名称,必须以“.m3u8”结尾,长度范围为【6,128】,非必须(默认值:playlist.m3u8)
+		},
+	}
+
+	result, err := bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	playURL := result.PlayUrls[0]
+	publishURL := result.PublishUrls[0]
+	fmt.Printf("create livechannel:%s  with config respones: playURL:%s, publishURL: %s\n", channelName, playURL, publishURL)
+
+	// 场景2 - 简单配置livechannel创建
+	simpleCfg := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS",
+		},
+	}
+	result, err = bucket.CreateLiveChannel(channelName, simpleCfg)
+	if err != nil {
+		HandleError(err)
+	}
+	playURL = result.PlayUrls[0]
+	publishURL = result.PublishUrls[0]
+	fmt.Printf("create livechannel:%s  with  simple config respones: playURL:%s, publishURL: %s\n", channelName, playURL, publishURL)
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PutObjectSample completed")
+}
+
+// PutLiveChannelStatusSample - 设置直播频道的状态的sample,有两种状态可选:enabled和disabled
+func PutLiveChannelStatusSample() {
+	channelName := "put-livechannel-status"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //指定转储的类型,只支持HLS, 必须
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1 - 设置livechannel的状态为disabled
+	err = bucket.PutLiveChannelStatus(channelName, "disabled")
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2 - 设置livechannel的状态为enalbed
+	err = bucket.PutLiveChannelStatus(channelName, "enabled")
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PutLiveChannelStatusSample completed")
+}
+
+// PostVodPlayListSample - 生成点播列表的sample
+func PostVodPlayListSample() {
+	channelName := "post-vod-playlist"
+	playlistName := "playlist.m3u8"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type:         "HLS", //指定转储的类型,只支持HLS, 必须
+			PlaylistName: "playlist.m3u8",
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	//这个阶段可以推流了...
+
+	endTime := time.Now().Add(-1 * time.Minute)
+	startTime := endTime.Add(-60 * time.Minute)
+	err = bucket.PostVodPlaylist(channelName, playlistName, startTime, endTime)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PostVodPlayListSampleSample completed")
+}
+
+// GetLiveChannelStatSample - 获取指定直播流频道当前推流的状态的sample
+func GetLiveChannelStatSample() {
+	channelName := "get-livechannel-stat"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //指定转储的类型,只支持HLS, 必须
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	stat, err := bucket.GetLiveChannelStat(channelName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	status := stat.Status
+	connectedTime := stat.ConnectedTime
+	remoteAddr := stat.RemoteAddr
+
+	audioBW := stat.Audio.Bandwidth
+	audioCodec := stat.Audio.Codec
+	audioSampleRate := stat.Audio.SampleRate
+
+	videoBW := stat.Video.Bandwidth
+	videoFrameRate := stat.Video.FrameRate
+	videoHeight := stat.Video.Height
+	videoWidth := stat.Video.Width
+
+	fmt.Printf("get channel stat:(%v, %v,%v, %v), audio(%v, %v, %v), video(%v, %v, %v, %v)\n", channelName, status, connectedTime, remoteAddr, audioBW, audioCodec, audioSampleRate, videoBW, videoFrameRate, videoHeight, videoWidth)
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("GetLiveChannelStatSample completed")
+}
+
+// GetLiveChannelInfoSample - 获取直播流频道的配置信息的sample
+func GetLiveChannelInfoSample() {
+	channelName := "get-livechannel-info"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //指定转储的类型,只支持HLS, 必须
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	info, err := bucket.GetLiveChannelInfo(channelName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	desc := info.Description
+	status := info.Status
+	fragCount := info.Target.FragCount
+	fragDuation := info.Target.FragDuration
+	playlistName := info.Target.PlaylistName
+	targetType := info.Target.Type
+
+	fmt.Printf("get channel stat:(%v,%v, %v), target(%v, %v, %v, %v)\n", channelName, desc, status, fragCount, fragDuation, playlistName, targetType)
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("GetLiveChannelInfoSample completed")
+}
+
+// GetLiveChannelHistorySample - 获取直播流频道的历史推流记录的sample
+func GetLiveChannelHistorySample() {
+	channelName := "get-livechannel-info"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //指定转储的类型,只支持HLS, 必须
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	//目前最多会返回指定LiveChannel最近的10次推流记录
+	history, err := bucket.GetLiveChannelHistory(channelName)
+	for _, record := range history.Record {
+		remoteAddr := record.RemoteAddr
+		startTime := record.StartTime
+		endTime := record.EndTime
+		fmt.Printf("get channel:%s history:(%v, %v, %v)\n", channelName, remoteAddr, startTime, endTime)
+	}
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("GetLiveChannelHistorySample completed")
+}
+
+// ListLiveChannelSample - 获取当前bucket下直播流频道的信息列表的sample
+func ListLiveChannelSample() {
+	channelName := "list-livechannel"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //指定转储的类型,只支持HLS, 必须
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:列出当前bucket下所有的livechannel
+	marker := ""
+	for {
+		//设定marker值,第一次执行时为”“,后续执行时以返回结果的nextmarker的值作为marker的值
+		result, err := bucket.ListLiveChannel(oss.Marker(marker))
+		if err != nil {
+			HandleError(err)
+		}
+
+		for _, channel := range result.LiveChannel {
+			fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0])
+		}
+
+		if result.IsTruncated {
+			marker = result.NextMarker
+		} else {
+			break
+		}
+	}
+
+	// 场景2:使用参数”max-keys“指定返回记录的最大个数, 但max-keys的值不能超过1000
+	result, err := bucket.ListLiveChannel(oss.MaxKeys(10))
+	if err != nil {
+		HandleError(err)
+	}
+	for _, channel := range result.LiveChannel {
+		fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0])
+	}
+
+	// 场景3;使用参数”prefix“过滤只列出包含”prefix“的值作为前缀的livechannel
+	// max-keys, prefix, maker参数可以组合使用
+	result, err = bucket.ListLiveChannel(oss.MaxKeys(10), oss.Prefix("list-"))
+	if err != nil {
+		HandleError(err)
+	}
+	for _, channel := range result.LiveChannel {
+		fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0])
+	}
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("ListLiveChannelSample completed")
+}
+
+// DeleteLiveChannelSample - 删除直播频道的信息列表的sample
+func DeleteLiveChannelSample() {
+	channelName := "delete-livechannel"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //指定转储的类型,只支持HLS, 必须
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteLiveChannel(channelName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("DeleteLiveChannelSample completed")
+}
+
+// SignRtmpURLSample - 创建签名推流地址的sample
+func SignRtmpURLSample() {
+	channelName := "sign-rtmp-url"
+	playlistName := "playlist.m3u8"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type:         "HLS", //指定转储的类型,只支持HLS, 必须
+			PlaylistName: "playlist.m3u8",
+		},
+	}
+
+	result, err := bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	playURL := result.PlayUrls[0]
+	publishURL := result.PublishUrls[0]
+	fmt.Printf("livechannel:%s, playURL:%s, publishURL: %s\n", channelName, playURL, publishURL)
+
+	signedRtmpURL, err := bucket.SignRtmpURL(channelName, playlistName, 3600)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Printf("livechannel:%s, sinedRtmpURL: %s\n", channelName, signedRtmpURL)
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PutObjectSample completed")
+}