diff --git a/README.md b/README.md
index 3816947..00116b4 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,11 @@ Bililive-go是一个支持多种直播平台的直播录制工具
www.huajiao.com |
滋瓷 |
+
+ | 抖音直播 |
+ live.douyin.com |
+ 滋瓷 |
+
## 依赖
diff --git a/src/cmd/bililive/internal/init.go b/src/cmd/bililive/internal/init.go
index 3bd4e04..9040d44 100644
--- a/src/cmd/bililive/internal/init.go
+++ b/src/cmd/bililive/internal/init.go
@@ -5,6 +5,7 @@ import (
_ "github.com/hr3lxphr6j/bililive-go/src/live/acfun"
_ "github.com/hr3lxphr6j/bililive-go/src/live/bilibili"
_ "github.com/hr3lxphr6j/bililive-go/src/live/cc"
+ _ "github.com/hr3lxphr6j/bililive-go/src/live/douyin"
_ "github.com/hr3lxphr6j/bililive-go/src/live/douyu"
_ "github.com/hr3lxphr6j/bililive-go/src/live/huajiao"
_ "github.com/hr3lxphr6j/bililive-go/src/live/huomao"
diff --git a/src/live/douyin/douyin.go b/src/live/douyin/douyin.go
new file mode 100644
index 0000000..4b25dcd
--- /dev/null
+++ b/src/live/douyin/douyin.go
@@ -0,0 +1,97 @@
+package douyin
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "github.com/hr3lxphr6j/requests"
+ "github.com/tidwall/gjson"
+
+ "github.com/hr3lxphr6j/bililive-go/src/live"
+ "github.com/hr3lxphr6j/bililive-go/src/live/internal"
+ "github.com/hr3lxphr6j/bililive-go/src/pkg/utils"
+)
+
+const (
+ domain = "live.douyin.com"
+ cnName = "抖音"
+
+ regRenderData = ``
+)
+
+func init() {
+ live.Register(domain, new(builder))
+}
+
+type builder struct{}
+
+func (b *builder) Build(url *url.URL) (live.Live, error) {
+ return &Live{
+ BaseLive: internal.NewBaseLive(url),
+ }, nil
+}
+
+type Live struct {
+ internal.BaseLive
+}
+
+func (l *Live) getData() (*gjson.Result, error) {
+ resp, err := requests.Get(l.Url.String(), live.CommonUserAgent)
+ if err != nil {
+ return nil, err
+ }
+ switch code := resp.StatusCode; code {
+ case http.StatusOK:
+ case http.StatusNotFound:
+ return nil, live.ErrRoomNotExist
+ default:
+ return nil, fmt.Errorf("failed to get page, code: %v, %w", code, live.ErrInternalError)
+ }
+
+ body, err := resp.Text()
+ if err != nil {
+ return nil, err
+ }
+ rawData := utils.Match1(regRenderData, body)
+ if rawData == "" {
+ return nil, fmt.Errorf("failed to get RENDER_DATA from page, %w", live.ErrInternalError)
+ }
+ unescapedRawData, err := url.QueryUnescape(rawData)
+ if err != nil {
+ return nil, err
+ }
+ result := gjson.Parse(unescapedRawData)
+ return &result, nil
+}
+
+func (l *Live) GetInfo() (info *live.Info, err error) {
+ data, err := l.getData()
+ if err != nil {
+ return nil, err
+ }
+ info = &live.Info{
+ Live: l,
+ HostName: data.Get("routeInitialProps.roomInfo.room.owner.nickname").String(),
+ RoomName: data.Get("routeInitialProps.roomInfo.anchor.nickname").String(),
+ Status: data.Get("routeInitialProps.roomInfo.room.status").Int() == 2,
+ }
+ return
+}
+
+func (l *Live) GetStreamUrls() (us []*url.URL, err error) {
+ data, err := l.getData()
+ if err != nil {
+ return nil, err
+ }
+ var urls []string
+ data.Get("routeInitialProps.roomInfo.room.stream_url.flv_pull_url").ForEach(func(key, value gjson.Result) bool {
+ urls = append(urls, value.String())
+ return true
+ })
+ return utils.GenUrls(urls...)
+}
+
+func (l *Live) GetPlatformCNName() string {
+ return cnName
+}