Sfoglia il codice sorgente

Merge pull request #92 from aliyun/livechannel

add api for livechannel
fengyu 6 anni fa
parent
commit
c3b4343f31
10 ha cambiato i file con 1375 aggiunte e 0 eliminazioni
  1. 30 0
      oss/auth.go
  2. 1 0
      oss/bucket_test.go
  3. 31 0
      oss/conn.go
  4. 40 0
      oss/conn_test.go
  5. 1 0
      oss/const.go
  6. 257 0
      oss/livechannel.go
  7. 428 0
      oss/livechannel_test.go
  8. 98 0
      oss/type.go
  9. 44 0
      sample/comm.go
  10. 445 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"
 )
 
@@ -64,6 +66,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
+}
+
 // newHeaderSorter is an additional function for function SignHeader.
 func newHeaderSorter(m map[string]string) *headerSorter {
 	hs := &headerSorter{

+ 1 - 0
oss/bucket_test.go

@@ -59,6 +59,7 @@ func (s *OssBucketSuite) SetUpSuite(c *C) {
 	testLogger.Println("test bucket started")
 }
 
+
 // TearDownSuite runs before each test or benchmark starts running.
 func (s *OssBucketSuite) TearDownSuite(c *C) {
 	for _, bucket := range []*Bucket{s.bucket, s.archiveBucket} {

+ 31 - 0
oss/conn.go

@@ -304,6 +304,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)
+}
+
 // handleBody handles request body
 func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64,
 	listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) {
@@ -633,6 +654,16 @@ func (um urlMaker) getSignURL(bucket, object, params string) string {
 	return fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params)
 }
 
+// getSignRtmpURL 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)
+}
+
 // buildURL builds URL
 func (um urlMaker) buildURL(bucket, object string) (string, string) {
 	var host = ""

+ 40 - 0
oss/conn_test.go

@@ -3,6 +3,8 @@ package oss
 import (
 	"net/http"
 	"os"
+	"strings"
+	"time"
 
 	. "gopkg.in/check.v1"
 )
@@ -142,3 +144,41 @@ func (s *OssConnSuite) TestConnToolFunc(c *C) {
 	err = jsonUnmarshal(fd, &out)
 	c.Assert(err, NotNil)
 }
+
+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)
+}
+
+func (s *OssConnSuite) TestGetRtmpSignedStr(c *C) {
+	cfg := getDefaultOssConfig()
+	um := urlMaker{}
+	um.Init(endpoint, false, false)
+	conn := Conn{cfg, &um, nil}
+
+	//Anonymous
+	channelName := "test-get-rtmp-signed-str"
+	playlistName := "playlist.m3u8"
+	expiration := time.Now().Unix() + 3600
+	params := map[string]interface{}{}
+	signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, params)
+	c.Assert(signedStr, Equals, "")
+}

+ 1 - 0
oss/const.go

@@ -127,6 +127,7 @@ const (
 	HTTPParamAccessKeyID   = "OSSAccessKeyId"
 	HTTPParamSignature     = "Signature"
 	HTTPParamSecurityToken = "security-token"
+	HTTPParamPlaylistName  = "playlistName"
 )
 
 // Other constants

+ 257 - 0
oss/livechannel.go

@@ -0,0 +1,257 @@
+package oss
+
+import (
+	"bytes"
+	"encoding/xml"
+	"fmt"
+	"io"
+	"net/http"
+	"strconv"
+	"time"
+)
+
+//
+// CreateLiveChannel    create a live-channel
+//
+// channelName  the name of the channel
+// config       configuration of the channel
+//
+// CreateLiveChannelResult  the result of create live-channel
+// error        nil if success, otherwise error
+//
+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 Set the status of the live-channel: enabled/disabled
+//
+// channelName  the name of the channel
+// status       enabled/disabled
+//
+// error        nil if success, otherwise error
+//
+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  create an playlist based on the specified playlist name, startTime and endTime
+//
+// channelName  the name of the channel
+// playlistName the name of the playlist, must end with ".m3u8"
+// startTime    the start time of the playlist
+// endTime      the endtime of the playlist
+//
+// error        nil if success, otherwise error
+//
+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})
+}
+
+// GetVodPlaylist  get the playlist based on the specified channelName, startTime and endTime
+//
+// channelName  the name of the channel
+// startTime    the start time of the playlist
+// endTime      the endtime of the playlist
+//
+// io.ReadCloser reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil.
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) GetVodPlaylist(channelName string, startTime, endTime time.Time) (io.ReadCloser, error) {
+	params := map[string]interface{}{}
+	params["vod"] = nil
+	params["startTime"] = strconv.FormatInt(startTime.Unix(), 10)
+	params["endTime"] = strconv.FormatInt(endTime.Unix(), 10)
+
+	resp, err := bucket.do("GET", channelName, params, nil, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Body, nil
+}
+
+//
+// GetLiveChannelStat   Get the state of the live-channel
+//
+// channelName  the name of the channel
+//
+// LiveChannelStat  the state of the live-channel
+// error        nil if success, otherwise error
+//
+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   Get the configuration info of the live-channel
+//
+// channelName  the name of the channel
+//
+// LiveChannelConfiguration the configuration info of the live-channel
+// error        nil if success, otherwise error
+//
+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    Get push records of live-channel
+//
+// channelName  the name of the channel
+//
+// LiveChannelHistory   push records
+// error        nil if success, otherwise error
+//
+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  list the live-channels
+//
+// options  Prefix: filter by the name start with the value of "Prefix"
+//          MaxKeys: the maximum count returned
+//          Marker: cursor from which starting list
+//
+// ListLiveChannelResult    live-channel list
+// error    nil if success, otherwise error
+//
+func (bucket Bucket) ListLiveChannel(options ...Option) (ListLiveChannelResult, error) {
+	var out ListLiveChannelResult
+
+	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    Delete the live-channel. When a client trying to stream the live-channel, the operation will fail. it will only delete the live-channel itself and the object generated by the live-channel will not be deleted.
+//
+// channelName  the name of the channel
+//
+// error        nil if success, otherwise error
+//
+func (bucket Bucket) DeleteLiveChannel(channelName string) error {
+	params := map[string]interface{}{}
+	params["live"] = nil
+
+	if channelName == "" {
+		return fmt.Errorf("invalid argument: channel name is empty")
+	}
+
+	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  Generate a RTMP push-stream signature URL for the trusted user to push the RTMP stream to the live-channel.
+//
+// channelName  the name of the channel
+// playlistName the name of the playlist, must end with ".m3u8"
+// expires      expiration (in seconds)
+//
+// string       singed rtmp push stream url
+// error        nil if success, otherwise error
+//
+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
+}

+ 428 - 0
oss/livechannel_test.go

@@ -0,0 +1,428 @@
+package oss
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssBucketLiveChannelSuite struct {
+	client *Client
+	bucket *Bucket
+}
+
+var _ = Suite(&OssBucketLiveChannelSuite{})
+
+// SetUpSuite 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...")
+}
+
+// TearDownSuite Run once after all tests or benchmarks
+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...")
+}
+
+// SetUpTest Run before each test or benchmark starts
+func (s *OssBucketLiveChannelSuite) SetUpTest(c *C) {
+
+}
+
+// TearDownTest	Run after each test or benchmark runs.
+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)
+
+	emptyChannelName := ""
+	err = s.bucket.DeleteLiveChannel(emptyChannelName)
+	c.Assert(err, NotNil)
+
+	longChannelName := randStr(65)
+	err = s.bucket.DeleteLiveChannel(longChannelName)
+	c.Assert(err, NotNil)
+
+	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) //The default value is 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)
+}
+
+// TestPostVodPlaylist
+func (s *OssBucketLiveChannelSuite) TestGetVodPlaylist(c *C) {
+	channelName := "test-get-vod-playlist"
+
+	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.GetVodPlaylist(channelName, 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)
+
+	invalidMaxKeys := -1
+	result, err = s.bucket.ListLiveChannel(MaxKeys(invalidMaxKeys))
+	c.Assert(err, NotNil)
+
+	for i := 0; i < 200; i++ {
+		channelName := fmt.Sprintf("%s-%03d", prefix, i)
+		err := s.bucket.DeleteLiveChannel(channelName)
+		c.Assert(err, IsNil)
+	}
+}
+
+// TestSignRtmpURL
+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 Get the play url of the live channel
+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 Get the push url of the live stream channel
+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
+}

+ 98 - 0
oss/type.go

@@ -466,3 +466,101 @@ type createBucketConfiguration struct {
 	XMLName      xml.Name         `xml:"CreateBucketConfiguration"`
 	StorageClass StorageClassType `xml:"StorageClass,omitempty"`
 }
+
+// LiveChannelConfiguration defines the configuration for live-channel
+type LiveChannelConfiguration struct {
+	XMLName     xml.Name          `xml:"LiveChannelConfiguration"`
+	Description string            `xml:"Description,omitempty"` //Description of live-channel, up to 128 bytes
+	Status      string            `xml:"Status,omitempty"`      //Specify the status of livechannel
+	Target      LiveChannelTarget `xml:"Target"`                //target configuration of live-channel
+	// use point instead of struct to avoid omit empty snapshot
+	Snapshot *LiveChannelSnapshot `xml:"Snapshot,omitempty"` //snapshot configuration of live-channel
+}
+
+// LiveChannelTarget target configuration of live-channel
+type LiveChannelTarget struct {
+	XMLName      xml.Name `xml:"Target"`
+	Type         string   `xml:"Type"`                   //the type of object, only supports HLS
+	FragDuration int      `xml:"FragDuration,omitempty"` //the length of each ts object (in seconds), in the range [1,100]
+	FragCount    int      `xml:"FragCount,omitempty"`    //the number of ts objects in the m3u8 object, in the range of [1,100]
+	PlaylistName string   `xml:"PlaylistName,omitempty"` //the name of m3u8 object, which must end with ".m3u8" and the length range is [6,128]
+}
+
+// LiveChannelSnapshot snapshot configuration of live-channel
+type LiveChannelSnapshot struct {
+	XMLName     xml.Name `xml:"Snapshot"`
+	RoleName    string   `xml:"RoleName,omitempty"`    //The role of snapshot operations, it sholud has write permission of DestBucket and the permission to send messages to the NotifyTopic.
+	DestBucket  string   `xml:"DestBucket,omitempty"`  //Bucket the snapshots will be written to. should be the same owner as the source bucket.
+	NotifyTopic string   `xml:"NotifyTopic,omitempty"` //Topics of MNS for notifying users of high frequency screenshot operation results
+	Interval    int      `xml:"Interval,omitempty"`    //interval of snapshots, threre is no snapshot if no I-frame during the interval time
+}
+
+// CreateLiveChannelResult the result of crete live-channel
+type CreateLiveChannelResult struct {
+	XMLName     xml.Name `xml:"CreateLiveChannelResult"`
+	PublishUrls []string `xml:"PublishUrls>Url"` //push urls list
+	PlayUrls    []string `xml:"PlayUrls>Url"`    //play urls list
+}
+
+// LiveChannelStat the result of get live-channel state
+type LiveChannelStat struct {
+	XMLName       xml.Name         `xml:"LiveChannelStat"`
+	Status        string           `xml:"Status"`        //Current push status of live-channel: Disabled,Live,Idle
+	ConnectedTime time.Time        `xml:"ConnectedTime"` //The time when the client starts pushing, format: ISO8601
+	RemoteAddr    string           `xml:"RemoteAddr"`    //The ip address of the client
+	Video         LiveChannelVideo `xml:"Video"`         //Video stream information
+	Audio         LiveChannelAudio `xml:"Audio"`         //Audio stream information
+}
+
+// LiveChannelVideo video stream information
+type LiveChannelVideo struct {
+	XMLName   xml.Name `xml:"Video"`
+	Width     int      `xml:"Width"`     //Width (unit: pixels)
+	Height    int      `xml:"Height"`    //Height (unit: pixels)
+	FrameRate int      `xml:"FrameRate"` //FramRate
+	Bandwidth int      `xml:"Bandwidth"` //Bandwidth (unit: B/s)
+}
+
+// LiveChannelAudio audio stream information
+type LiveChannelAudio struct {
+	XMLName    xml.Name `xml:"Audio"`
+	SampleRate int      `xml:"SampleRate"` //SampleRate
+	Bandwidth  int      `xml:"Bandwidth"`  //Bandwidth (unit: B/s)
+	Codec      string   `xml:"Codec"`      //Encoding forma
+}
+
+// LiveChannelHistory the result of GetLiveChannelHistory, at most return up to lastest 10 push records
+type LiveChannelHistory struct {
+	XMLName xml.Name     `xml:"LiveChannelHistory"`
+	Record  []LiveRecord `xml:"LiveRecord"` //push records list
+}
+
+// LiveRecord push recode
+type LiveRecord struct {
+	XMLName    xml.Name  `xml:"LiveRecord"`
+	StartTime  time.Time `xml:"StartTime"`  //StartTime, format: ISO8601
+	EndTime    time.Time `xml:"EndTime"`    //EndTime, format: ISO8601
+	RemoteAddr string    `xml:"RemoteAddr"` //The ip address of remote client
+}
+
+// ListLiveChannelResult the result of ListLiveChannel
+type ListLiveChannelResult struct {
+	XMLName     xml.Name          `xml:"ListLiveChannelResult"`
+	Prefix      string            `xml:"Prefix"`      //Filter by the name start with the value of "Prefix"
+	Marker      string            `xml:"Marker"`      //cursor from which starting list
+	MaxKeys     int               `xml:"MaxKeys"`     //The maximum count returned. the default value is 100. it cannot be greater than 1000.
+	IsTruncated bool              `xml:"IsTruncated"` //Indicates whether all results have been returned, "true" indicates partial results returned while "false" indicates all results have been returned
+	NextMarker  string            `xml:"NextMarker"`  //NextMarker indicate the Marker value of the next request
+	LiveChannel []LiveChannelInfo `xml:"LiveChannel"` //The infomation of live-channel
+}
+
+// LiveChannelInfo the infomation of live-channel
+type LiveChannelInfo struct {
+	XMLName      xml.Name  `xml:"LiveChannel"`
+	Name         string    `xml:"Name"`            //The name of live-channel
+	Description  string    `xml:"Description"`     //Description of live-channel
+	Status       string    `xml:"Status"`          //Status: disabled or enabled
+	LastModified time.Time `xml:"LastModified"`    //Last modification time, format: ISO8601
+	PublishUrls  []string  `xml:"PublishUrls>Url"` //push urls list
+	PlayUrls     []string  `xml:"PlayUrls>Url"`    //play urls list
+}

+ 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 deletes the test bucket and its objects
 func DeleteTestBucketAndObject(bucketName string) error {
 	// New client

+ 445 - 0
sample/livechannel.go

@@ -0,0 +1,445 @@
+package sample
+
+import (
+	"fmt"
+	"io/ioutil"
+	"time"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CreateLiveChannelSample Samples for create a live-channel
+func CreateLiveChannelSample() {
+	channelName := "create-livechannel"
+	//create bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Case 1 - Create live-channel with Completely configure
+	config := oss.LiveChannelConfiguration{
+		Description: "sample-for-livechannel", //description information, up to 128 bytes
+		Status:      "enabled",                //enabled or disabled
+		Target: oss.LiveChannelTarget{
+			Type:         "HLS",                          //the type of object, only supports HLS, required
+			FragDuration: 10,                             //the length of each ts object (in seconds), in the range [1,100], default: 5
+			FragCount:    4,                              //the number of ts objects in the m3u8 object, in the range of [1,100], default: 3
+			PlaylistName: "test-get-channel-status.m3u8", //the name of m3u8 object, which must end with ".m3u8" and the length range is [6,128],default: 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)
+
+	// Case 2 - Create live-channel only specified type of target which is required
+	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 Set the status of the live-channel 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", //the type of object, only supports HLS, required
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Case 1 - Set the status of live-channel to disabled
+	err = bucket.PutLiveChannelStatus(channelName, "disabled")
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Case 2 - Set the status of live-channel to enabled
+	err = bucket.PutLiveChannelStatus(channelName, "enabled")
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PutLiveChannelStatusSample completed")
+}
+
+// PostVodPlayListSample Sample for generate playlist
+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", //the type of object, only supports HLS, required
+			PlaylistName: "playlist.m3u8",
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	//This stage you can push live stream, and after that you could generator playlist
+
+	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")
+}
+
+// GetVodPlayListSample Sample for generate playlist and return the content of the playlist
+func GetVodPlayListSample() {
+	channelName := "get-vod-playlist"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type:         "HLS", //the type of object, only supports HLS, required
+			PlaylistName: "playlist.m3u8",
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	//This stage you can push live stream, and after that you could generator playlist
+
+	endTime := time.Now().Add(-1 * time.Minute)
+	startTime := endTime.Add(-60 * time.Minute)
+	body, err := bucket.GetVodPlaylist(channelName, startTime, endTime)
+	if err != nil {
+		HandleError(err)
+	}
+	defer body.Close()
+
+	data, err := ioutil.ReadAll(body)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Printf("content of playlist is:%v\n", string(data))
+
+	err = DeleteTestBucketAndLiveChannel(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PostVodPlayListSampleSample completed")
+}
+
+// GetLiveChannelStatSample Sample for get the state of live-channel
+func GetLiveChannelStatSample() {
+	channelName := "get-livechannel-stat"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //the type of object, only supports HLS, required
+		},
+	}
+
+	_, 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 for get the configuration infomation of live-channel
+func GetLiveChannelInfoSample() {
+	channelName := "get-livechannel-info"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //the type of object, only supports HLS, required
+		},
+	}
+
+	_, 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 for get push records of live-channel
+func GetLiveChannelHistorySample() {
+	channelName := "get-livechannel-info"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //the type of object, only supports HLS, required
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	//at most return up to lastest 10 push records
+	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 Samples for list live-channels with specified bucket name
+func ListLiveChannelSample() {
+	channelName := "list-livechannel"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //the type of object, only supports HLS, required
+		},
+	}
+
+	_, err = bucket.CreateLiveChannel(channelName, config)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Case 1: list all the live-channels
+	marker := ""
+	for {
+		// Set the marker value, the first time is "", the value of NextMarker that returned should as the marker in the next time
+		// At most return up to lastest 100 live-channels if "max-keys" is not specified
+		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
+		}
+	}
+
+	// Case 2: Use the parameter "max-keys" to specify the maximum number of records returned, the value of max-keys cannot exceed 1000
+	// if "max-keys" the default value is 100
+	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])
+	}
+
+	// Case 3: Only list the live-channels with the value of parameter "prefix" as prefix
+	// max-keys, prefix, maker parameters can be combined
+	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 for delete live-channel
+func DeleteLiveChannelSample() {
+	channelName := "delete-livechannel"
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	config := oss.LiveChannelConfiguration{
+		Target: oss.LiveChannelTarget{
+			Type: "HLS", //the type of object, only supports HLS, required
+		},
+	}
+
+	_, 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 for generate a RTMP push-stream signature URL for the trusted user to push the RTMP stream to the live channel.
+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", //the type of object, only supports HLS, required
+			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("SignRtmpURLSample completed")
+}