diff --git a/analysis_options.yaml b/analysis_options.yaml index 4995189..2d6f3c4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -14,7 +14,7 @@ linter: - prefer_constructors_over_static_methods - prefer_contains - annotate_overrides - - await_futures + - await_future analyzer: exclude: diff --git a/example/example.dart b/example/example.dart index 3f0d43c..4f939f8 100644 --- a/example/example.dart +++ b/example/example.dart @@ -2,11 +2,10 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart'; Future main() async { var yt = YoutubeExplode(); - var video = await yt.videos.get(VideoId('https://www.youtube.com/watch?v=bo_efYhYU2A')); - var streamManifest = await yt.videos.streamsClient.getManifest(VideoId('https://www.youtube.com/watch?v=bo_efYhYU2A')); + var video = + await yt.videos.get('https://www.youtube.com/watch?v=bo_efYhYU2A'); print('Title: ${video.title}'); - print(streamManifest.streams()); // Close the YoutubeExplode's http client. yt.close(); diff --git a/example/video_download.dart b/example/video_download.dart index 6a8ba54..215931f 100644 --- a/example/video_download.dart +++ b/example/video_download.dart @@ -14,19 +14,12 @@ Future main() async { var url = stdin.readLineSync().trim(); - // Get the video url. - var id = YoutubeExplode.parseVideoId(url); - if (id == null) { - console.writeLine('Invalid video id or url.'); - exit(1); - } - // Save the video to the download directory. Directory('downloads').createSync(); console.hideCursor(); // Download the video. - await download(id); + await download(url); yt.close(); console.showCursor(); @@ -34,24 +27,28 @@ Future main() async { } Future download(String id) async { - // Get the video media stream. - var mediaStream = await yt.getVideoMediaStream(id); + // Get video metadata. + var video = await yt.videos.get(id); + + // Get the video manifest. + var manifest = await yt.videos.streamsClient.getManifest(id); + var streams = manifest.getAudioOnly(); // Get the last audio track (the one with the highest bitrate). - var audio = mediaStream.audio.last; + var audio = streams.last; + var audioStream = yt.videos.streamsClient.get(audio); // Compose the file name removing the unallowed characters in windows. - var fileName = - '${mediaStream.videoDetails.title}.${audio.container.toString()}' - .replaceAll('Container.', '') - .replaceAll(r'\', '') - .replaceAll('/', '') - .replaceAll('*', '') - .replaceAll('?', '') - .replaceAll('"', '') - .replaceAll('<', '') - .replaceAll('>', '') - .replaceAll('|', ''); + var fileName = '${video.title}.${audio.container.name.toString()}' + .replaceAll('Container.', '') + .replaceAll(r'\', '') + .replaceAll('/', '') + .replaceAll('*', '') + .replaceAll('?', '') + .replaceAll('"', '') + .replaceAll('<', '') + .replaceAll('>', '') + .replaceAll('|', ''); var file = File('downloads/$fileName'); // Create the StreamedRequest to track the download status. @@ -60,24 +57,26 @@ Future download(String id) async { var output = file.openWrite(mode: FileMode.writeOnlyAppend); // Track the file download status. - var len = audio.size; + var len = audio.size.totalBytes; var count = 0; var oldProgress = -1; // Create the message and set the cursor position. - var msg = 'Downloading `${mediaStream.videoDetails.title}`: \n'; - var row = console.cursorPosition.row; - var col = msg.length - 2; - console.cursorPosition = Coordinate(row, 0); - console.write(msg); + var msg = 'Downloading `${video.title}`(.${audio.container.name}): \n'; + print(msg); + // var row = console.cursorPosition.row; +// var col = msg.length - 2; +// console.cursorPosition = Coordinate(row, 0); +// console.write(msg); + // Listen for data received. - await for (var data in audio.downloadStream()) { + await for (var data in audioStream) { count += data.length; var progress = ((count / len) * 100).round(); if (progress != oldProgress) { - console.cursorPosition = Coordinate(row, col); - console.write('$progress%'); +// console.cursorPosition = Coordinate(row, col); + print('$progress%'); oldProgress = progress; } output.add(data); diff --git a/lib/src/channels/channel_client.dart b/lib/src/channels/channel_client.dart index 370aa7a..7c1f54a 100644 --- a/lib/src/channels/channel_client.dart +++ b/lib/src/channels/channel_client.dart @@ -16,14 +16,21 @@ class ChannelClient { ChannelClient(this._httpClient); /// Gets the metadata associated with the specified channel. - Future get(ChannelId id) async { + /// [id] must be either a [ChannelId] or a string + /// which is parsed to a [ChannelId] + Future get(dynamic id) async { + var channelPage = await ChannelPage.get(_httpClient, id.value); return Channel(id, channelPage.channelTitle, channelPage.channelLogoUrl); } /// Gets the metadata associated with the channel of the specified user. - Future getByUsername(Username username) async { + /// [username] must be either a [Username] or a string + /// which is parsed to a [Username] + Future getByUsername(dynamic username) async { + username = Username.fromString(username); + var channelPage = await ChannelPage.getByUsername(_httpClient, username.value); return Channel(ChannelId(channelPage.channelId), channelPage.channelTitle, @@ -32,7 +39,8 @@ class ChannelClient { /// Gets the metadata associated with the channel /// that uploaded the specified video. - Future getByVideo(VideoId videoId) async { + Future getByVideo(dynamic videoId) async { + videoId = VideoId.fromString(videoId); var videoInfoResponse = await VideoInfoResponse.get(_httpClient, videoId.value); var playerReponse = videoInfoResponse.playerResponse; diff --git a/lib/src/channels/channel_id.dart b/lib/src/channels/channel_id.dart index a9bb68b..bf1f902 100644 --- a/lib/src/channels/channel_id.dart +++ b/lib/src/channels/channel_id.dart @@ -14,6 +14,7 @@ class ChannelId extends Equatable { } } + /// Returns true if the given id is a valid channel id. static bool validateChannelId(String id) { if (id.isNullOrWhiteSpace) { return false; @@ -50,6 +51,15 @@ class ChannelId extends Equatable { return null; } + /// Converts [obj] to a [ChannelId] by calling .toString on that object. + /// If it is already a [ChannelId], [obj] is returned + factory ChannelId.fromString(dynamic obj) { + if (obj is ChannelId) { + return obj; + } + return ChannelId(obj.toString()); + } + @override String toString() => '$value'; diff --git a/lib/src/channels/username.dart b/lib/src/channels/username.dart index 97f8b9d..3ee2c15 100644 --- a/lib/src/channels/username.dart +++ b/lib/src/channels/username.dart @@ -13,6 +13,7 @@ class Username { } } + /// Returns true if the given username is a valid username. static bool validateUsername(String name) { if (name.isNullOrWhiteSpace) { return false; @@ -25,6 +26,7 @@ class Username { return !RegExp('[^0-9a-zA-Z]').hasMatch(name); } + /// Parses a username from a url. static String parseUsername(String nameOrUrl) { if (nameOrUrl.isNullOrWhiteSpace) { return null; @@ -42,4 +44,13 @@ class Username { } return null; } + + /// Converts [obj] to a [Username] by calling .toString on that object. + /// If it is already a [Username], [obj] is returned + factory Username.fromString(dynamic obj) { + if (obj is Username) { + return obj; + } + return Username(obj.toString()); + } } diff --git a/lib/src/exceptions/youtube_explode_exception.dart b/lib/src/exceptions/youtube_explode_exception.dart index 8c1958a..6b0d1ed 100644 --- a/lib/src/exceptions/youtube_explode_exception.dart +++ b/lib/src/exceptions/youtube_explode_exception.dart @@ -1,6 +1,11 @@ /// Parent class for domain exceptions thrown by [YoutubeExplode] abstract class YoutubeExplodeException implements Exception { + /// Generic message. final String message; + /// YoutubeExplodeException(this.message); + + @override + String toString(); } diff --git a/lib/src/extensions/helpers_extension.dart b/lib/src/extensions/helpers_extension.dart index f695701..4f49d78 100644 --- a/lib/src/extensions/helpers_extension.dart +++ b/lib/src/extensions/helpers_extension.dart @@ -68,6 +68,7 @@ extension UriUtility on Uri { /// extension GetOrNull on Map { + /// Get a value from a map V getValue(K key) { var v = this[key]; if (v == null) { @@ -79,6 +80,7 @@ extension GetOrNull on Map { /// extension GetOrNullMap on Map { + /// Get a map inside a map Map get(String key) { var v = this[key]; if (v == null) { diff --git a/lib/src/playlists/playlist.dart b/lib/src/playlists/playlist.dart index e15e86c..dce05ae 100644 --- a/lib/src/playlists/playlist.dart +++ b/lib/src/playlists/playlist.dart @@ -1,6 +1,7 @@ import '../common/common.dart'; import 'playlist_id.dart'; +/// YouTube playlist metadata. class Playlist { /// Playlist ID. final PlaylistId id; diff --git a/lib/src/playlists/playlist_client.dart b/lib/src/playlists/playlist_client.dart index aa57b21..247c60b 100644 --- a/lib/src/playlists/playlist_client.dart +++ b/lib/src/playlists/playlist_client.dart @@ -14,7 +14,9 @@ class PlaylistClient { PlaylistClient(this._httpClient); /// Gets the metadata associated with the specified playlist. - Future get(PlaylistId id) async { + Future get(dynamic id) async { + id = PlaylistId.fromString(id); + var response = await PlaylistResponse.get(_httpClient, id.value); return Playlist( id, @@ -26,7 +28,8 @@ class PlaylistClient { } /// Enumerates videos included in the specified playlist. - Stream