Skip to content

[Bug]: Audio/Video stream emits no chunks despite valid size (3.0.5) #379

@RaulCatalinas

Description

@RaulCatalinas

What happened?

Environment:

OS: Windows 11
youtube_explode_dart version: 3.0.5

I'm trying to download audio/video streams to disk. The stream completes without emitting any chunks despite having a valid size (result.size.totalBytes returns the correct value). The file ends up being 0kb.

I verified the issue is not related to writing to disk — writing hardcoded bytes to a file works correctly.

Here's the relevant code:

interact_api.dart

import 'dart:async' show Future, Stream;

import 'package:logkeeper/logkeeper.dart' show LogKeeper;
import 'package:youtube_explode_dart/js_challenge.dart' show BaseEJSSolver;
import 'package:youtube_explode_dart/youtube_explode_dart.dart'
    show StreamInfoIterableExt, YoutubeExplode;

import '/utils/file_utils.dart' show cleanInvalidChars;
import 'core_utils.dart' show getDenoSolver;

typedef YouTubeStream = Stream<List<int>>;
typedef YouTubeStreamResult = ({YouTubeStream stream, YoutubeExplode yt});

class InteractApi {
  static InteractApi? _instance;
  late final BaseEJSSolver _solver;

  InteractApi._internal();

  static InteractApi get instance {
    _instance ??= InteractApi._internal();
    return _instance!;
  }

  static Future<void> initialize() async {
    try {
      LogKeeper.info('Initializing InteractApi...');
      instance._solver = await getDenoSolver();
      LogKeeper.info('✓ InteractApi initialized successfully');
    } catch (e, stackTrace) {
      LogKeeper.critical(
        '💀 FATAL: Failed to initialize InteractApi: ${e.toString()}',
      );
      LogKeeper.critical('Error StackTrace: $stackTrace');
      rethrow;
    }
  }

  static Future<String> getTitle(String url) async {
    final yt = YoutubeExplode(jsSolver: instance._solver);

    try {
      final video = await yt.videos.get(url);

      return cleanInvalidChars(video.title);
    } finally {
      yt.close();
    }
  }

  static Future<String> getThumbnailUrl(String url) async {
    final yt = YoutubeExplode(jsSolver: instance._solver);

    try {
      final video = await yt.videos.get(url);

      return video.thumbnails.highResUrl;
    } finally {
      yt.close();
    }
  }

  static Future<YouTubeStreamResult> getAudioStream(String url) async {
    final yt = YoutubeExplode(jsSolver: instance._solver);

    final manifest = await yt.videos.streams.getManifest(url);
    final audioStreams = manifest.audioOnly;

    if (audioStreams.isEmpty) {
      throw Exception('No audio streams available');
    }

    final audioInfo = audioStreams.withHighestBitrate();

    return (stream: yt.videos.streams.get(audioInfo), yt: yt);
  }

  static Future<YouTubeStreamResult> getVideoStream(String url) async {
    final yt = YoutubeExplode(jsSolver: instance._solver);

    final manifest = await yt.videos.streams.getManifest(url);
    final videoStreams = manifest.videoOnly;

    if (videoStreams.isEmpty) {
      throw Exception('No video streams available');
    }

    final videoInfo = videoStreams.withHighestBitrate();

    return (stream: yt.videos.streams.get(videoInfo), yt: yt);
  }
}

core_utils.dart

import 'dart:io';

import 'package:flutter/services.dart' show rootBundle;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'
    show getApplicationSupportDirectory;
import 'package:system_info3/system_info3.dart'
    show SysInfo, ProcessorArchitecture;
import 'package:youtube_explode_dart/js_challenge.dart' show BaseEJSSolver;
import 'package:youtube_explode_dart/solvers.dart' show DenoEJSSolver;

import '/constants/paths.dart' show denoExecutables;

Future<String> _prepareDeno(String assetPath) async {
  final filename = assetPath.split('/').last;
  final dir = await getApplicationSupportDirectory();
  final destPath = p.join(dir.path, filename);

  final file = File(destPath);

  if (!await file.exists()) {
    final data = await rootBundle.load(assetPath);
    await file.writeAsBytes(data.buffer.asUint8List(), flush: true);

    await Process.run('chmod', ['u+x', destPath]);
  }

  return destPath;
}

Future<BaseEJSSolver> getDenoSolver() async {
  final denoAssetPath = switch ((
    Platform.operatingSystem,
    SysInfo.kernelArchitecture,
  )) {
    ('macos', ProcessorArchitecture.arm64) => denoExecutables['macos-arm64']!,
    ('macos', ProcessorArchitecture.x86_64) => denoExecutables['macos-x86_64']!,
    ('windows', _) => denoExecutables['windows']!,
    ('linux', _) => denoExecutables['linux']!,
    (var os, _) => throw Exception('Unsupported OS: $os'),
  };

  final denoPath = Platform.isWindows
      ? denoAssetPath
      : await _prepareDeno(denoAssetPath);

  return await DenoEJSSolver.init(denoExe: denoPath);
}

Stacktrace / error logs.

Package (youtube_explode_dart) version

3.0.5

Checklist

  • I have provided a descriptive title
  • I have searched the issues of this repository and believe that this is not a duplicate
  • I have tested with the latest version of the library
  • (If relevant) I can open the video/playlist in a incognito/private window without being logged in.
  • I have sponsored the project

Additional notes

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions