성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# MAUI - 유튜브 동영상을 MediaElement로 재생하는 방법</h1> <p> 지난 글까지 해서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# MAUI - 안드로이드 "Share" 대상으로 등록하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13635'>https://www.sysnet.pe.kr/2/0/13635</a> </pre> <br /> 유튜브로부터 동영상 URL을 전달받는 것까지 완료를 했습니다. 그렇다면 이제 그 URL에 해당하는 영상을 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13624'>MediaElement</a>로 볼 수 있으면 좋을 텐데요, 아쉽게도 여기에는 한 가지 문제가 있습니다.<br /> <br /> 다들 아시는 것처럼, Youtube의 동영상에서 "Copy video URL" 메뉴로 복사한 링크는, 예를 들어 "<a target='tab' href='https://youtu.be/GPAe6d_NE8E'>https://youtu.be/GPAe6d_NE8E</a>" 이런 형식인데요, 이건 "동영상을 포함한 유튜브 페이지"의 URL입니다. 그래서 그 URL을 직접 MediaElement의 Source에 지정할 수는 없습니다.<br /> <br /> 다행히도 이런 문제(?)를 해결하려는 사용자들의 시도가 youtube-dl같은 도구로 있었는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 유튜브(youtube) 동영상을 다운로드하는 프로그램 youtube-dl 소개 ; <a target='tab' href='https://www.sysnet.pe.kr/0/0/488'>https://www.sysnet.pe.kr/0/0/488</a> youtube-dl을 파이썬으로 실행하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/0/0/549'>https://www.sysnet.pe.kr/0/0/549</a> </pre> <br /> 물론 C#으로도 YoutubeExplode 등의 라이브러리를 이용해 쉽게 구현할 수 있습니다.<br /> <a name='youtube_explode'></a> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > YoutubeExplode ; <a target='tab' href='https://github.com/Tyrrrz/YoutubeExplode'>https://github.com/Tyrrrz/YoutubeExplode</a> </pre> <br /> 따라서 해당 패키지를 참조 추가하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Install-Package YoutubeExplode </pre> <br /> 다음과 같이 코드를 사용하면, 동영상의 진짜 URL, 즉 다운로드 가능한 URL을 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Xml; using YoutubeExplode; using YoutubeExplode.Videos.Streams; namespace ConsoleApp1; internal class Program { // Install-Package <a target='tab' href='https://www.nuget.org/packages/YoutubeExplode/'>YoutubeExplode</a> static async Task Main(string[] args) { string url = await GetLink("https://youtu.be/GPAe6d_NE8E"); Console.WriteLine(url); } private static async Task<string> GetLink(string url) { <span style='color: blue; font-weight: bold'>var youtube = new YoutubeClient(); var streamManifest = await youtube.Videos.Streams.GetManifestAsync(url); var streamInfo = streamManifest.GetMuxedStreams() .Where(s => s.Container == Container.Mp4) .GetWithHighestVideoQuality(); return streamInfo.Url</span> } } </pre> <br /> 실행하면 이런 식의 아주 복잡한 URL이 나오는데,<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> https://rr4---sn-3u-bh2zz.googlevideo.com/videoplayback?<span style='color: blue; font-weight: bold'>expire=1716988995</span>&ei=49dWZtG9KdCQs8IP1fmK2Aw&ip=59.7.103.152&id=o-AOuGI1vXV3-aM3P5bYsMNTYEa4e6St4FtPXiVfiu1Co5&itag=18&source=youtube&requiressl=yes&xpc=EgVo2aDSNQ%3D%3D&mh=H4&mm=31%2C26&mn=sn-3u-bh2zz%2Csn-oguesnds&ms=au%2Conr&mv=m&mvi=4&pl=17&initcwndbps=706250&vprv=1&svpuc=1&mime=video%2Fmp4&rqh=1&gir=yes&clen=12009656&ratebypass=yes&dur=265.218&lmt=1387758073146630&mt=1716967012&fvip=2&c=ANDROID_TESTSUITE&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cvprv%2Csvpuc%2Cmime%2Crqh%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&sig=AJf...[생략]...7aAw%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AH...[생략]...AExg%3D%3D </div><br /> <br /> expire가 있으니, 아마도 cache한 저 URL이 영구적이지 않음을 예상케 합니다. 일단 저렇게 실제 동영상의 URL을 구했으면 이후에는 일반적인 절차로 HTTP 통신을 이용한 다운로드를 할 수 있습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WebClient wc = new WebClient(); byte [] contents = wc.DownloadData(streamInfo.Url); File.WriteAllBytes(@"C:\temp\test.mp4", contents); </pre> <br /> 이외에도 유튜브 자료의 Video 또는 Audio만 선택해서 다운로드하는 방법 등 다양한 활용이 있는데요, 그런 예제 코드는 <a target='tab' href='https://github.com/Tyrrrz/YoutubeExplode/blob/master/Readme.md#usage'>github README</a>에 모두 상세하게 설명하고 있으니 참고하시고.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 이것을 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13635#wrm'>지난 예제</a>에 곁들이면 WeakReferenceMessenger에서 대충 이렇게 처리할 수 있을 텐데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WeakReferenceMessenger.Default.Register<UrlShareMessage>(this, async (r, m) => { var urlText = m.Value; this.Title = urlText; var youtube = new YoutubeClient(); var streamManifest = await youtube.Videos.Streams.GetManifestAsync(urlText); var streamInfo = streamManifest.GetMuxedStreams() .Where(s => s.Container == Container.Mp4) .GetWithHighestVideoQuality(); <span style='color: blue; font-weight: bold'>this.mediaPlayer.Source = streamInfo.Url;</span> }); </pre> <br /> 아쉽게도 저렇게 하면 <a target='tab' href='https://stackoverflow.com/questions/41085736/android-os-networkonmainthreadexception-in-async-await-block'>Android.OS.NetworkOnMainThreadException 예외</a>가 발생합니다. 그런데 예외 이름이 좀 특이하죠? ^^ 메인 스레드에서 Network 통신을 하고 있어서 예외를 발생시킨다는 의미인데, 스마트폰에서의 앱이 UI 반응을 할 수 없도록 만드는 상황을 강제로 막은 것이라고 보면 됩니다. (예전에 윈도우의 경우 Store App에서는 동기 호출을 모두 없앤 정책과 유사합니다.)<br /> <br /> 따라서, 이것을 정상 동작하게 만들려면 일단 별도의 스레드로 빼야 합니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>await Task.Run(async() =></span> { var streamManifest = await youtube.Videos.Streams.GetManifestAsync(urlText); var streamInfo = streamManifest.GetMuxedStreams() .Where(s => s.Container == Container.Mp4) .GetWithHighestVideoQuality(); this.mediaPlayer.Source = streamInfo.Url; }); </pre> <a name='cross_thread'></a> <br /> 하지만, 그렇게 되면 mediaPlayer UI 요소에 접근하는 스레드가 Main 스레드가 아니므로 "this.mediaPlayer.Source = streamInfo.Url;" 코드에서 다시 이런 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Java.Lang.IllegalStateException: 'Player is accessed on the wrong thread. Current thread: 'Thread-12' Expected thread: 'main' See https://developer.android.com/guide/topics/media/issues/player-accessed-on-wrong-thread' </pre> <br /> 따라서 최종적으로는 이렇게 코드가 나와야 합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WeakReferenceMessenger.Default.Register<UrlShareMessage>(this, async (r, m) => { var urlText = m.Value; this.Title = urlText; var youtube = new YoutubeClient(); <span style='color: blue; font-weight: bold'>await Task.Run</span>(async() => { var streamManifest = await youtube.Videos.Streams.GetManifestAsync(urlText); var streamInfo = streamManifest.GetMuxedStreams() .Where(s => s.Container == Container.Mp4) .GetWithHighestVideoQuality(); <span style='color: blue; font-weight: bold'>await MainThread.InvokeOnMainThreadAsync</span>(() => { this.mediaPlayer.Source = streamInfo.Url; }); }); }); </pre> <br /> 어쨌든, 위의 코드까지 완료하고 실행하면 ^^ 이제 정상적으로 MediaElement에서 Youtube 동영상을 재생할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Whisper API Python 사용법 ; <a target='tab' href='https://wooiljeong.github.io/python/whisper-api/'>https://wooiljeong.github.io/python/whisper-api/</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1470
(왼쪽의 숫자를 입력해야 합니다.)