来自 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.
,即从ready
到testing
,流状态已经是active
和health
是 good
,有什么我遗漏的吗?
显然是流分辨率太低造成的,相机是360x640
(垂直>__<),创建分辨率为720p
的新流解决了这个问题。
我在广播中取得了不错的成绩
&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.
,即从ready
到testing
,流状态已经是active
和health
是 good
,有什么我遗漏的吗?
显然是流分辨率太低造成的,相机是360x640
(垂直>__<),创建分辨率为720p
的新流解决了这个问题。