-
-
Save mminer/43d86530a9479a3b5c725f7f118b64be to your computer and use it in GitHub Desktop.
| using System; | |
| using System.Text.RegularExpressions; | |
| using UnityEngine; | |
| /// <summary> | |
| /// Holds the result of parsing the ytInitialPlayerResponse JSON from a YouTube page. | |
| /// </summary> | |
| /// <remarks> | |
| /// This is an incomplete list of fields in ytInitialPlayerResponse. | |
| /// The full object contains many more, but we only care about a few. | |
| /// </remarks> | |
| [Serializable] | |
| public struct YouTubePlayerResponse | |
| { | |
| [Serializable] | |
| public struct PlayabilityStatus | |
| { | |
| public string reason; | |
| public string status; | |
| } | |
| [Serializable] | |
| public struct StreamingData | |
| { | |
| [Serializable] | |
| public struct Format | |
| { | |
| public int bitrate; | |
| public string fps; | |
| public string mimeType; | |
| public string quality; | |
| public string url; | |
| /// <summary> | |
| /// Whether this format is compatible with Unity's VideoPlayer. | |
| /// </summary> | |
| // TODO: this is probably needlessly restrictive | |
| public bool IsCompatible => mimeType.Contains("video/mp4"); | |
| } | |
| public Format[] formats; | |
| } | |
| [Serializable] | |
| public struct VideoDetails | |
| { | |
| public string shortDescription; | |
| public string title; | |
| } | |
| public PlayabilityStatus playabilityStatus; | |
| public StreamingData streamingData; | |
| public VideoDetails videoDetails; | |
| // Example of unplayable video: https://www.youtube.com/watch?v=qm5q1o7ofnc | |
| public bool IsPlayable => playabilityStatus.status != "ERROR"; | |
| public static YouTubePlayerResponse FromJson(string json) | |
| { | |
| return JsonUtility.FromJson<YouTubePlayerResponse>(json); | |
| } | |
| public static YouTubePlayerResponse? FromPageSource(string pageSource) | |
| { | |
| // Extract the JSON from the JavaScript in the HTML. | |
| var regex = new Regex(@"ytInitialPlayerResponse\s*=\s*(\{.+?\})\s*;", RegexOptions.Multiline); | |
| var match = regex.Match(pageSource); | |
| if (!match.Success) | |
| { | |
| return null; | |
| } | |
| var json = match.Result("$1"); | |
| return FromJson(json); | |
| } | |
| } |
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using UnityEngine; | |
| using UnityEngine.Networking; | |
| public enum YouTubeRequestResult | |
| { | |
| InProgress, | |
| Error, | |
| Success, | |
| } | |
| /// <summary> | |
| /// Provides a method to find URLs to a YouTube video's raw video files. | |
| /// </summary> | |
| public class YouTubeRequest | |
| { | |
| public string Error { get; private set; } | |
| public YouTubeRequestResult Result { get; private set; } | |
| public YouTubePlayerResponse.StreamingData.Format BestQualityFormat => Formats | |
| .OrderByDescending(format => format.bitrate) | |
| .First(); | |
| public List<YouTubePlayerResponse.StreamingData.Format> Formats => _playerResponse.streamingData.formats | |
| .Where(format => format.IsCompatible) | |
| .ToList(); | |
| private YouTubePlayerResponse _playerResponse; | |
| private readonly string _youTubeUrl; | |
| /// <summary> | |
| /// Creates a request to find a YouTube video files. | |
| /// </summary> | |
| /// <param name="youTubeUrl">YouTube video URL.</param> | |
| public YouTubeRequest(string youTubeUrl) | |
| { | |
| if (!YouTubeUtils.TryNormalizeYouTubeUrl(youTubeUrl, out _youTubeUrl)) | |
| { | |
| throw new ArgumentException("Invalid YouTube URL.", nameof(youTubeUrl)); | |
| } | |
| } | |
| /// <summary> | |
| /// Downloads a list of video files for a YouTube video. | |
| /// </summary> | |
| public IEnumerator SendRequest() | |
| { | |
| Result = YouTubeRequestResult.InProgress; | |
| Debug.Log($"Fetching YouTube page source from {_youTubeUrl}"); | |
| // Fetch the page source. That is, get the HTML that the browser downloads when you visit a YouTube page. | |
| string pageSource; | |
| using (var request = UnityWebRequest.Get(_youTubeUrl)) | |
| { | |
| yield return request.SendWebRequest(); | |
| if (request.result != UnityWebRequest.Result.Success) | |
| { | |
| ReportError($"Error fetching YouTube page: {request.error}"); | |
| yield break; | |
| } | |
| pageSource = request.downloadHandler.text; | |
| } | |
| // Extract video details from the page HTML. | |
| var playerResponse = YouTubePlayerResponse.FromPageSource(pageSource); | |
| if (!playerResponse.HasValue) | |
| { | |
| ReportError("Unable to parse video details from YouTube page."); | |
| yield break; | |
| } | |
| _playerResponse = playerResponse.Value; | |
| Debug.Log($"Downloaded details for YouTube video: \"{playerResponse.Value.videoDetails.title}\""); | |
| if (!playerResponse.Value.IsPlayable) | |
| { | |
| ReportError($"YouTube video unplayable: {playerResponse.Value.playabilityStatus.reason}"); | |
| yield break; | |
| } | |
| Result = YouTubeRequestResult.Success; | |
| } | |
| private void ReportError(string error) | |
| { | |
| Error = error; | |
| Result = YouTubeRequestResult.Error; | |
| } | |
| } |
| using System; | |
| using System.Web; | |
| /// <summary> | |
| /// Utility functions for interacting with YouTube. | |
| /// </summary> | |
| public static class YouTubeUtils | |
| { | |
| /// <summary> | |
| /// Pulls the video ID from a YouTube URL. | |
| /// </summary> | |
| /// <param name="youTubeUrl">YouTube URL to extract ID from.</param> | |
| /// <returns>Canonical YouTube video ID (e.g. VZBYoN-iHkE).</returns> | |
| public static string ExtractVideoId(string youTubeUrl) | |
| { | |
| if (string.IsNullOrEmpty(youTubeUrl)) | |
| { | |
| return null; | |
| } | |
| // YouTube URLs come in a few different formats. | |
| // Normalize the URL such that the video ID appears in the query string. | |
| youTubeUrl = youTubeUrl | |
| .Trim() | |
| .Replace("youtu.be/", "youtube.com/watch?v=") | |
| .Replace("youtube.com/embed/", "youtube.com/watch?v=") | |
| .Replace("/watch#", "/watch?"); | |
| if (youTubeUrl.Contains("/v/")) | |
| { | |
| var absolutePath = new Uri(youTubeUrl).AbsolutePath; | |
| absolutePath = absolutePath.Replace("/v/", "/watch?v="); | |
| youTubeUrl = $"https://youtube.com{absolutePath}"; | |
| } | |
| // The URL should now contain a query string of the format v={video-id}. | |
| var queryString = new Uri(youTubeUrl).Query; | |
| var query = HttpUtility.ParseQueryString(queryString); | |
| return query.Get("v"); | |
| } | |
| /// <summary> | |
| /// Normalizes a YouTube URL to the format https://youtube.com/watch?v={video-id}. | |
| /// </summary> | |
| /// <param name="youTubeUrl">YouTube URL to normalize.</param> | |
| /// <param name="normalizedYouTubeUrl">Normalized YouTube URL.</param> | |
| /// <returns>Whether normalization was successful and the URL is valid.</returns> | |
| public static bool TryNormalizeYouTubeUrl(string youTubeUrl, out string normalizedYouTubeUrl) | |
| { | |
| var videoId = ExtractVideoId(youTubeUrl); | |
| if (string.IsNullOrEmpty(videoId)) | |
| { | |
| normalizedYouTubeUrl = null; | |
| return false; | |
| } | |
| normalizedYouTubeUrl = $"https://www.youtube.com/watch?v={videoId}&gl=US&hl=en&has_verified=1&bpctr=9999999999"; | |
| return true; | |
| } | |
| } |
| using System; | |
| using System.Collections; | |
| using UnityEngine; | |
| using UnityEngine.Video; | |
| public class YouTubeVideoPlayer : MonoBehaviour | |
| { | |
| public VideoPlayer videoPlayer; | |
| [InspectorName("YouTube URL")] | |
| public string youTubeUrl; | |
| private void Awake() | |
| { | |
| if (videoPlayer == null) | |
| { | |
| videoPlayer = GetComponent<VideoPlayer>(); | |
| } | |
| } | |
| private void Start() | |
| { | |
| StartCoroutine(SetVideoPlayerUrl()); | |
| } | |
| private IEnumerator SetVideoPlayerUrl() | |
| { | |
| if (videoPlayer == null) | |
| { | |
| Debug.LogError("No video player."); | |
| yield break; | |
| } | |
| var request = new YouTubeRequest(youTubeUrl); | |
| yield return request.SendRequest(); | |
| if (request.Result == YouTubeRequestResult.Error) | |
| { | |
| Debug.LogError($"Failed to fetch YouTube video details: {request.Error}"); | |
| yield break; | |
| } | |
| Debug.Log("Fetched YouTube formats."); | |
| try | |
| { | |
| videoPlayer.url = request.BestQualityFormat.url; | |
| } | |
| catch (InvalidOperationException) | |
| { | |
| Debug.LogError("Failed to find any compatible formats."); | |
| } | |
| } | |
| } |
Hi,
I use Unity 2020.3.19f1 and i have an error
Can't play movie []
UnityEngine.StackTraceUtility:ExtractStackTrace ()
YoutubeVideoPlayer/d__4:MoveNext () (at Assets/YoutubeVideoPlayer.cs:47)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)
have you an idea ?
Thank's
@aphaena: It’s tough to say what the problem might be. It might be helpful to print the JSON string that YouTubePlayerResponse:FromPageSource returns, or what the final URL that it sets on videoPlayer.url is.
It's perfect when i active my gameobject with youtube video player after my app is started.
Thank's you for your share
Beautifull job :)
I don't know about you all but for me highest quality lags a lot even with a fiber connection on PC. But the code seems to work !
hello the video starts after 9-10 seconds how can i make that this time to 1-2 seconds
Thank you
Usage: attach to a game object that has a
VideoPlayercomponent attached, or set aVideoPlayerfrom another game object in the inspector field.This doesn't work in WebGL builds due to CORS.