I am attempting to upload videos to YouTube via the YouTube API v3 from my Flutter web application. The upload process works as expected for smaller videos, but when attempting to upload a larger video (e.g., 200MB), I encounter the error: "Invalid array length," and the upload fails.
Below is the relevant portion of my code:
// Automatic FlutterFlow importsimport '/backend/backend.dart';import '/flutter_flow/flutter_flow_theme.dart';import '/flutter_flow/flutter_flow_util.dart';import '/custom_code/widgets/index.dart'; // Imports other custom widgetsimport '/custom_code/actions/index.dart'; // Imports custom actionsimport '/flutter_flow/custom_functions.dart'; // Imports custom functionsimport 'package:flutter/material.dart';// Begin custom widget code// DO NOT REMOVE OR MODIFY THE CODE ABOVE!import 'dart:async'; // For Streamimport 'dart:convert'; // For JSON decodingimport 'dart:io' as io; // For File handling (conditionally for non-web platforms)import 'package:googleapis_auth/auth_io.dart';import 'package:googleapis/youtube/v3.dart';import 'package:file_picker/file_picker.dart';import 'dart:typed_data'; // For Uint8Listimport 'package:http/http.dart' as http;import 'package:flutter/foundation.dart'; // For kIsWebclass UploadVideoButton extends StatefulWidget { const UploadVideoButton({ super.key, this.width, this.height, }); final double? width; final double? height; @override State<UploadVideoButton> createState() => _UploadVideoButtonState();}class _UploadVideoButtonState extends State<UploadVideoButton> { bool isUploading = false; double uploadProgress = 0.0; // Replace these placeholders with your Google API credentials static const String clientId = //value removed from here ; static const String clientSecret = //value removed from here; static const String refreshToken = //value removed from here; static const List<String> scopes = [YouTubeApi.youtubeUploadScope]; Future<void> uploadVideo() async { try { setState(() { isUploading = true; uploadProgress = 0.0; }); // Authenticate and get an authorized client var authClient = await _getAuthClient(); final youtubeApi = YouTubeApi(authClient); // Pick a video file FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.video, ); if (result != null) { Uint8List? fileBytes = result.files.single.bytes; //String? filePath = result.files.single.path; // Use fileBytes for web or if path is unavailable Uint8List fileData = fileBytes!; // For non-web platforms, you may still want to use the file path (commented out here) // if (filePath != null && !kIsWeb) { // fileData = io.File(filePath).readAsBytesSync(); // } int totalBytes = fileData.length; // Create byte stream compatible with YouTube API Stream<List<int>> byteStream = Stream.fromIterable( [fileData.buffer.asUint8List()]); // Wrap bytes into List<int> // Progress-tracking stream var progressStream = _createProgressStream(byteStream, totalBytes); var video = Video(); video.snippet = VideoSnippet() ..title = 'Sample Video Title' ..description = 'Uploaded via Flutter App' ..tags = ['Flutter', 'YouTube API']; video.status = VideoStatus()..privacyStatus = 'public'; var media = Media(progressStream, totalBytes); // Upload the video var response = await youtubeApi.videos.insert( video, ['snippet', 'status'], uploadMedia: media, ); setState(() { isUploading = false; }); // Show success message _showSnackbar('Video uploaded successfully: https://www.youtube.com/watch?v=${response.id}'); } else { setState(() { isUploading = false; }); _showSnackbar('No video selected'); } } catch (e) { setState(() { isUploading = false; }); _showSnackbar('Error: $e'); } } Stream<List<int>> _createProgressStream( Stream<List<int>> sourceStream, int totalBytes) { int bytesSent = 0; const chunkSize = 256 * 1024; // 256 KB per chunk (adjust as necessary) return sourceStream.asyncMap((chunk) async { bytesSent += chunk.length; // Update progress setState(() { uploadProgress = bytesSent / totalBytes; }); return chunk; }); } /// Authenticates and returns an authorized client Future<AuthClient> _getAuthClient() async { final tokenEndpoint = Uri.parse('https://oauth2.googleapis.com/token'); final response = await http.post(tokenEndpoint, body: {'client_id': clientId,'client_secret': clientSecret,'refresh_token': refreshToken,'grant_type': 'refresh_token', }); if (response.statusCode != 200) { throw Exception('Failed to refresh token: ${response.body}'); } final Map<String, dynamic> json = jsonDecode(response.body); final credentials = AccessCredentials( AccessToken('Bearer', json['access_token'], DateTime.now().add(Duration(seconds: json['expires_in'])).toUtc(), ), null, scopes, ); return authenticatedClient(http.Client(), credentials); } /// Displays a snackbar with a message void _showSnackbar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } @override Widget build(BuildContext context) { return Stack( children: [ if (isUploading) Container( color: Colors.black.withOpacity(0.5), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ //CircularProgressIndicator(value: uploadProgress), //SizedBox(height: 16), Text('Uploading... ${(uploadProgress * 100).toStringAsFixed(2)}%', style: TextStyle(color: Colors.white), ), ], ), ), ), if (!isUploading) SizedBox( width: widget.width, height: widget.height, child: ElevatedButton( onPressed: uploadVideo, child: const Text('Upload Video'), ), ), ], ); }}
NOTE
also progress text are not working as excepted ..it shows 0% then 100% after upload is almost done