I'm a software developer and we are working on a new app (stream.attn.live). We have a strange problem, streaming from our platform (stream.attn.live), because sometimes we have only scheduled stream and not going live after that. The code we are using is the same and that happens with a specific Youtube account and very rarely with others. This is a link for one of my tests: https://www.youtube.com/watch?v=9oQW4JmY9YEIt always stays scheduled, but with other YT accounts it goes live. Sometimes that also happens with other accounts, but always with the mentioned one. I can't find any logic behind that, because I tried a lot of things, did a lot of researches and nothing changed at all. I added our main code for YT broadcasting below. Another problem is that if a user stops the stream without any silence at the end (1,2 seconds), it cuts the last 2,3 seconds from the end of the stream. If there is silence, it is the whole stream recorded. Please feel free to ask me some questions and I will try to provide you with some additional info. Thanks in advance!Best regards,Svetoslav Kazakov
using AttnLive.Data.Models;using AttnLive.Web.Services.Contracts;using Google.Apis.Auth.OAuth2;using Google.Apis.Auth.OAuth2.Flows;using Google.Apis.Auth.OAuth2.Responses;using Google.Apis.Services;using Google.Apis.Util.Store;using Google.Apis.YouTube.v3;using Google.Apis.YouTube.v3.Data;using System;using System.Diagnostics;using System.IO;using System.Threading;using System.Threading.Tasks;namespace AttnLive.Web.Services{ public class YouTubeLiveStreaming { private readonly IUsersService _usersService; public StreamingYouTubeOptions Options { get; } //set only via Secret Manager public YouTubeLiveStreaming( IUsersService usersService, StreamingYouTubeOptions optionsAccessor) { this._usersService = usersService; this.Options = optionsAccessor; } public async Task<String> CreateLiveBroadcastEventAsync( UserAccounts userAccount, String title, String description, DateTime eventStartDate, bool? youtubeIsPublic) { string watchUrl = "-2"; try { //UserCredential credentials; string[] scopes = new string[] { "https://www.googleapis.com/auth/youtube" }; // user basic profile UserCredential credentials; string dataStoreFolder = Environment.CurrentDirectory; using (var streams = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read)) { // create authorization code flow with clientSecrets GoogleAuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { DataStore = new FileDataStore(dataStoreFolder), ClientSecrets = GoogleClientSecrets.Load(streams).Secrets }); FileDataStore fileStore = new FileDataStore(dataStoreFolder); TokenResponse tokenResponse = await fileStore.GetAsync<TokenResponse>(userAccount.Id.ToString()); credentials = new UserCredential(authorizationCodeFlow, userAccount.Id.ToString(), tokenResponse); } var service = new YouTubeService(new BaseClientService.Initializer { HttpClientInitializer = credentials, ApplicationName = "attnlive" }); //Create Broadcast string privacy = string.Empty; if (youtubeIsPublic == true) privacy = "public"; else if (youtubeIsPublic == false) privacy = "private"; else if (youtubeIsPublic == null) privacy = "unlisted"; var broadcast = new LiveBroadcast { Kind = "youtube#liveBroadcast", Snippet = new LiveBroadcastSnippet { Title = title, Description = description, ScheduledStartTime = eventStartDate }, Status = new LiveBroadcastStatus { /*if(YoutubeIsPublic == true)*/ PrivacyStatus = privacy, LifeCycleStatus = "ready" }, ContentDetails = new LiveBroadcastContentDetails { MonitorStream = new MonitorStreamInfo { EnableMonitorStream = false }, //This works instead of Transition method EnableAutoStart = true } }; var requestLiveBroadcast = service.LiveBroadcasts.Insert(broadcast, "id,snippet,status,contentDetails"); var responseLiveBroadcast = requestLiveBroadcast.Execute(); //Create Stream var stream = new LiveStream { Kind = "youtube#liveStream", Snippet = new LiveStreamSnippet { Title = title, Description = description }, Status = new LiveStreamStatus { StreamStatus = "ready" }, Cdn = new CdnSettings { FrameRate = "30fps", IngestionType = "rtmp", Resolution = "1080p" }, ContentDetails = new LiveStreamContentDetails { IsReusable = true } }; var requestLiveStream = service.LiveStreams.Insert(stream, "snippet,cdn,contentDetails,status"); var responseLiveStream = requestLiveStream.Execute(); var ingestionAddress = responseLiveStream.Cdn.IngestionInfo.IngestionAddress; var streamName = responseLiveStream.Cdn.IngestionInfo.StreamName; //Bind the LiveBroadcast and Live Stream var liveBroadcastBind = service.LiveBroadcasts.Bind(responseLiveBroadcast.Id, "id,status,contentDetails"); liveBroadcastBind.StreamId = responseLiveStream.Id; responseLiveBroadcast = liveBroadcastBind.Execute(); var liveStreamRequest = service.LiveStreams.List(""); watchUrl = liveBroadcastBind.Id; //Enable Fmbeg string path = Environment.CurrentDirectory +"\\wwwroot\\images\\"; string userImagePath = "youtube.png"; if (!string.IsNullOrEmpty(userAccount.YoutubeMutexImage)) userImagePath = userAccount.YoutubeMutexImage; else if (!string.IsNullOrEmpty(userAccount.ImagePath)) userImagePath = userAccount.ImagePath; string arguments = string.Format(this.Options.StreamingYouTubeFfmpegParams, path + userImagePath, userAccount.Id, streamName); using (var process = new Process()) { process.StartInfo.FileName = this.Options.StreamingYouTubeFfmpegFilePath; // relative path. absolute path works too. process.StartInfo.Arguments = arguments; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; Thread.Sleep(this.Options.FfmpegStartDelay); process.Start(); userAccount.YoutubeStreamingProcessId = process.Id.ToString() +"|" + responseLiveBroadcast.Id; await this._usersService.UpdateUserAccountAsync(userAccount); } //LiveBroadcast.Transition - the LiveStream Status should be active to run the step below // Thread.Sleep(20000); //var broadCastTransTesting // = service.LiveBroadcasts.Transition( // LiveBroadcastsResource.TransitionRequest.BroadcastStatusEnum.Testing, responseLiveBroadcast.Id, "status"); //broadCastTransTesting.Execute(); //This should be working if EnableAutoStart for broadcast is not true or does not exists at all //LiveBroadcast.Transition - the Broadcast LifeCycleStatus should be Testing to run the step below //Thread.Sleep(this.Options.StreamingYouTubeDelay); //miliseconds //var broadCastTransLive //= service.LiveBroadcasts.Transition( // LiveBroadcastsResource.TransitionRequest.BroadcastStatusEnum.Live, responseLiveBroadcast.Id, "status"); //broadCastTransLive.Execute(); } catch (Exception ex) { return ex.Message; } return watchUrl; } public async Task<String> StopLiveBroadcastEventAsync(UserAccounts userAccount) { try { if (userAccount.YoutubeStreamingProcessId != null) { string[] values = userAccount.YoutubeStreamingProcessId.Split("|"); string encoderSoftwareProcessId = values[0]; string youtubeBroadcastId = values[1]; userAccount.YoutubeStreamingProcessId = null; await this._usersService.UpdateUserAccountAsync(userAccount); Process s = Process.GetProcessById(Int32.Parse(encoderSoftwareProcessId)); if (s != null) { Thread.Sleep(this.Options.StreamingYouTubeKillDelay); s.Kill(); } //UserCredential credentials; string[] scopes = new string[] { "https://www.googleapis.com/auth/youtube" }; // user basic profile UserCredential credentials; string dataStoreFolder = Environment.CurrentDirectory; using (var streams = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read)) { // create authorization code flow with clientSecrets GoogleAuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { DataStore = new FileDataStore(dataStoreFolder), ClientSecrets = GoogleClientSecrets.Load(streams).Secrets }); FileDataStore fileStore = new FileDataStore(dataStoreFolder); TokenResponse tokenResponse = await fileStore.GetAsync<TokenResponse>(userAccount.Id.ToString()); credentials = new UserCredential(authorizationCodeFlow, userAccount.Id.ToString(), tokenResponse); } var service = new YouTubeService(new BaseClientService.Initializer { HttpClientInitializer = credentials, ApplicationName = "attnlive" }); //Thread.Sleep(108000); var broadCastTransTesting = service.LiveBroadcasts.Transition( LiveBroadcastsResource.TransitionRequest.BroadcastStatusEnum.Complete, youtubeBroadcastId, "status"); broadCastTransTesting.Execute(); return youtubeBroadcastId; } } catch (Exception) { } return "-1"; } }}