来自 Youtube 直播的 InvalidTransition API

InvalidTransition from Youtube Live Streaming API

我在广播中取得了不错的成绩

&youtube.LiveBroadcastListResponse{
 Etag:    "\"aaa-3cgQ/bbb\"",
 EventId: "",
 Items:   {
     &youtube.LiveBroadcast{
         ContentDetails: &youtube.LiveBroadcastContentDetails{
             BoundStreamId:               "ccc-ddd",
             BoundStreamLastUpdateTimeMs: "2017-01-30T09:02:38.977Z",
             ClosedCaptionsType:          "closedCaptionsDisabled",
             EnableClosedCaptions:        false,
             EnableContentEncryption:     true,
             EnableDvr:                   true,
             EnableEmbed:                 true,
             EnableLowLatency:            false,
             MonitorStream:               &youtube.MonitorStreamInfo{
                 BroadcastStreamDelayMs: 1,
                 EmbedHtml:              "<iframe width=\"425\" height=\"344\" src=\"https://www.youtube.com/embed/eee?autoplay=1&livemonitor=1\" frameborder=\"0\" allowfullscreen></iframe>",
                 EnableMonitorStream:    true,
                 ForceSendFields:        nil,
                 NullFields:             nil,
             },
             Projection:      "rectangular",
             RecordFromStart: true,
             StartWithSlate:  true,
             ForceSendFields: nil,
             NullFields:      nil,
         },
         Etag:    "\"aaa-3cgQ/fff\"",
         Id:      "eee",
         Kind:    "youtube#liveBroadcast",
         Snippet: &youtube.LiveBroadcastSnippet{
             ActualEndTime:      "",
             ActualStartTime:    "",
             ChannelId:          "UCccc-ggg",
             Description:        "",
             IsDefaultBroadcast: false,
             LiveChatId:         "Cg0KC0hfUkFFaW4zUUVJ",
             PublishedAt:        "2017-01-30T04:59:19.000Z",
             ScheduledEndTime:   "2017-01-31T02:00:00.000Z",
             ScheduledStartTime: "2017-01-31T00:00:00.000Z",
             Thumbnails:         &youtube.ThumbnailDetails{
                 Default: &youtube.Thumbnail{
                     Height:          90,
                     Url:             "https://i.ytimg.com/vi/eee/default_live.jpg",
                     Width:           120,
                     ForceSendFields: nil,
                     NullFields:      nil,
                 },
                 High: &youtube.Thumbnail{
                     Height:          360,
                     Url:             "https://i.ytimg.com/vi/eee/hqdefault_live.jpg",
                     Width:           480,
                     ForceSendFields: nil,
                     NullFields:      nil,
                 },
                 Maxres: (*youtube.Thumbnail)(nil),
                 Medium: &youtube.Thumbnail{
                     Height:          180,
                     Url:             "https://i.ytimg.com/vi/eee/mqdefault_live.jpg",
                     Width:           320,
                     ForceSendFields: nil,
                     NullFields:      nil,
                 },
                 Standard:        (*youtube.Thumbnail)(nil),
                 ForceSendFields: nil,
                 NullFields:      nil,
             },
             Title:           "Test broadcast from iOS",
             ForceSendFields: nil,
             NullFields:      nil,
         },
         Statistics: (*youtube.LiveBroadcastStatistics)(nil),
         Status:     &youtube.LiveBroadcastStatus{
             LifeCycleStatus:       "ready",
             LiveBroadcastPriority: "",
             PrivacyStatus:         "unlisted",
             RecordingStatus:       "notRecording",
             ForceSendFields:       nil,
             NullFields:            nil,
         },
         TopicDetails:    (*youtube.LiveBroadcastTopicDetails)(nil),
         ServerResponse:  googleapi.ServerResponse{},
         ForceSendFields: nil,
         NullFields:      nil,
     },
 },
 Kind:          "youtube#liveBroadcastListResponse",
 NextPageToken: "",
 PageInfo:      &youtube.PageInfo{
     ResultsPerPage:  5,
     TotalResults:    0,
     ForceSendFields: nil,
     NullFields:      nil,
 },
 PrevPageToken:   "",
 TokenPagination: (*youtube.TokenPagination)(nil),
 VisitorId:       "",
 ServerResponse:  googleapi.ServerResponse{
     HTTPStatusCode: 200,
     Header:         {
         "Server":                 {"GSE"},
         "Expires":                {"Mon, 30 Jan 2017 11:34:04 GMT"},
         "Date":                   {"Mon, 30 Jan 2017 11:34:04 GMT"},
         "Cache-Control":          {"private, max-age=0, must-revalidate, no-transform"},
         "Etag":                   {"\"aaa-3cgQ/bbb\""},
         "Vary":                   {"Origin", "X-Origin"},
         "Content-Type":           {"application/json; charset=UTF-8"},
         "X-Frame-Options":        {"SAMEORIGIN"},
         "X-Content-Type-Options": {"nosniff"},
         "X-Xss-Protection":       {"1; mode=block"},
         "Content-Length":         {"1989"},
         "Alt-Svc":                {"quic=\":443\"; ma=2592000; v=\"35,34\""},
     },
 },
 ForceSendFields: nil,
 NullFields:      nil,
}

对 Stream 的回应也很好:

&youtube.LiveStreamListResponse{
 Etag:    "\"aaa-3cgQ/iii-Sg\"",
 EventId: "",
 Items:   {
     &youtube.LiveStream{
         Cdn: &youtube.CdnSettings{
             Format:        "360p",
             FrameRate:     "30fps",
             IngestionInfo: &youtube.IngestionInfo{
                 BackupIngestionAddress: "rtmp://b.rtmp.youtube.com/live2?backup=1",
                 IngestionAddress:       "rtmp://a.rtmp.youtube.com/live2",
                 StreamName:             "3zjv-0cp2-wtet-0779",
                 ForceSendFields:        nil,
                 NullFields:             nil,
             },
             IngestionType:   "rtmp",
             Resolution:      "360p",
             ForceSendFields: nil,
             NullFields:      nil,
         },
         ContentDetails: (*youtube.LiveStreamContentDetails)(nil),
         Etag:           "\"aaa-3cgQ/hhh\"",
         Id:             "ccc-ddd",
         Kind:           "youtube#liveStream",
         Snippet:        &youtube.LiveStreamSnippet{
             ChannelId:       "UCccc-ggg",
             Description:     "",
             IsDefaultStream: false,
             PublishedAt:     "2017-01-30T09:02:39.000Z",
             Title:           "Broadcast: eee App: 16 ID: 16",
             ForceSendFields: nil,
             NullFields:      nil,
         },
         Status: &youtube.LiveStreamStatus{
             HealthStatus: &youtube.LiveStreamHealthStatus{
                 ConfigurationIssues:   nil,
                 LastUpdateTimeSeconds: 0x0,
                 Status:                "good",
                 ForceSendFields:       nil,
                 NullFields:            nil,
             },
             StreamStatus:    "active",
             ForceSendFields: nil,
             NullFields:      nil,
         },
         ServerResponse:  googleapi.ServerResponse{},
         ForceSendFields: nil,
         NullFields:      nil,
     },
 },
 Kind:          "youtube#liveStreamListResponse",
 NextPageToken: "",
 PageInfo:      &youtube.PageInfo{
     ResultsPerPage:  5,
     TotalResults:    0,
     ForceSendFields: nil,
     NullFields:      nil,
 },
 PrevPageToken:   "",
 TokenPagination: (*youtube.TokenPagination)(nil),
 VisitorId:       "",
 ServerResponse:  googleapi.ServerResponse{
     HTTPStatusCode: 200,
     Header:         {
         "Date":                   {"Mon, 30 Jan 2017 11:35:54 GMT"},
         "X-Frame-Options":        {"SAMEORIGIN"},
         "X-Xss-Protection":       {"1; mode=block"},
         "Content-Length":         {"1008"},
         "Content-Type":           {"application/json; charset=UTF-8"},
         "X-Content-Type-Options": {"nosniff"},
         "Server":                 {"GSE"},
         "Alt-Svc":                {"quic=\":443\"; ma=2592000; v=\"35,34\""},
         "Expires":                {"Mon, 30 Jan 2017 11:35:54 GMT"},
         "Cache-Control":          {"private, max-age=0, must-revalidate, no-transform"},
         "Etag":                   {"\"aaa-3cgQ/iii-Sg\""},
         "Vary":                   {"Origin", "X-Origin"},
     },
 },
 ForceSendFields: nil,
 NullFields:      nil,
 }

但是当我尝试为 eee 转换到 testing 时,它显示错误:

&googleapi.Error{
     Code:    403,
     Message: "Invalid transition",
     Body:    "{\n \"error\": {\n  \"errors\": [\n   {\n    \"domain\": \"youtube.liveBroadcast\",\n    \"reason\": \"invalidTransition\",\n    \"message\": \"Invalid transition\",\n    \"extendedHelp\": \"https://developers.google.com/youtube/v3/live/docs/liveBroadcasts/transition#params\"\n   }\n  ],\n  \"code\": 403,\n  \"message\": \"Invalid transition\"\n }\n}\n",
     Header:  {},
     Errors:  {
         {Reason:"invalidTransition", Message:"Invalid transition"},
     },
 }

文档中说:The live broadcast can't transition from its current status to the requested status.,即从readytesting,流状态已经是activehealthgood,有什么我遗漏的吗?

显然是流分辨率太低造成的,相机是360x640(垂直>__<),创建分辨率为720p的新流解决了这个问题。