-
-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Plugin: Add web support & improve structure
- Loading branch information
1 parent
79c2bd5
commit 9ce4f4b
Showing
5 changed files
with
415 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,121 +1,32 @@ | ||
import 'dart:io'; | ||
import 'dart:convert'; | ||
import 'dart:typed_data'; | ||
import 'package:flutter/services.dart'; | ||
|
||
var _kChannel = const MethodChannel('flutter_media_metadata'); | ||
|
||
class MetadataRetriever { | ||
static Future<Metadata> fromFile( | ||
File file, { | ||
bool createNewInstance: false, | ||
}) async { | ||
var metadata = await _kChannel.invokeMethod( | ||
'MetadataRetriever', | ||
{ | ||
'filePath': file.path, | ||
'createNewInstance': createNewInstance, | ||
}, | ||
); | ||
metadata['filePath'] = file.path; | ||
return Metadata.fromJson(metadata); | ||
} | ||
} | ||
|
||
class Metadata { | ||
final String? trackName; | ||
final List<String>? trackArtistNames; | ||
final String? albumName; | ||
final String? albumArtistName; | ||
final int? trackNumber; | ||
final int? albumLength; | ||
final int? year; | ||
final String? genre; | ||
final String? authorName; | ||
final String? writerName; | ||
final int? discNumber; | ||
final String? mimeType; | ||
final int? trackDuration; | ||
final int? bitrate; | ||
final Uint8List? albumArt; | ||
final String filePath; | ||
|
||
const Metadata({ | ||
this.trackName, | ||
this.trackArtistNames, | ||
this.albumName, | ||
this.albumArtistName, | ||
this.trackNumber, | ||
this.albumLength, | ||
this.year, | ||
this.genre, | ||
this.authorName, | ||
this.writerName, | ||
this.discNumber, | ||
this.mimeType, | ||
this.trackDuration, | ||
this.bitrate, | ||
this.albumArt, | ||
required this.filePath, | ||
}); | ||
|
||
factory Metadata.fromJson(dynamic map) => Metadata( | ||
trackName: map['metadata']['trackName'], | ||
trackArtistNames: map['metadata']['trackArtistNames'] != null | ||
? map['metadata']['trackArtistNames'].split('/') | ||
: null, | ||
albumName: map['metadata']['albumName'], | ||
albumArtistName: map['metadata']['albumArtistName'], | ||
trackNumber: _parse(map['metadata']['trackNumber']), | ||
albumLength: _parse(map['metadata']['albumLength']), | ||
year: _parse(map['metadata']['year']), | ||
genre: map['genre'], | ||
authorName: map['metadata']['authorName'], | ||
writerName: map['metadata']['writerName'], | ||
discNumber: _parse(map['metadata']['discNumber']), | ||
mimeType: map['metadata']['mimeType'], | ||
trackDuration: _parse(map['metadata']['trackDuration']), | ||
bitrate: _parse(map['metadata']['bitrate']), | ||
albumArt: map['albumArt'], | ||
filePath: map['filePath'], | ||
); | ||
|
||
Map<String, dynamic> toJson() => { | ||
'trackName': trackName, | ||
'trackArtistNames': trackArtistNames, | ||
'albumName': albumName, | ||
'albumArtistName': albumArtistName, | ||
'trackNumber': trackNumber, | ||
'albumLength': albumLength, | ||
'year': year, | ||
'genre': genre, | ||
'authorName': authorName, | ||
'writerName': writerName, | ||
'discNumber': discNumber, | ||
'mimeType': mimeType, | ||
'trackDuration': trackDuration, | ||
'bitrate': bitrate, | ||
'filePath': filePath, | ||
}; | ||
|
||
@override | ||
String toString() => JsonEncoder.withIndent(' ').convert(toJson()); | ||
} | ||
|
||
int? _parse(dynamic value) { | ||
if (value == null) { | ||
return null; | ||
} | ||
if (value is int) { | ||
return value; | ||
} else if (value is String) { | ||
try { | ||
try { | ||
return int.parse(value); | ||
} catch (_) { | ||
return int.parse(value.split('/').first); | ||
} | ||
} catch (_) {} | ||
} | ||
return null; | ||
} | ||
/// ## flutter_media_metadata | ||
/// | ||
/// A Flutter plugin to read metadata of media files. | ||
/// | ||
/// MIT License. | ||
/// Copyright (c) 2021-2022, Hitesh Kumar Saini <[email protected]>. | ||
/// | ||
/// _Minimal Example_ | ||
/// ```dart | ||
/// final metadata = MetadataRetriever.fromBytes(byteData); | ||
/// String? trackName = metadata.trackName; | ||
/// List<String>? trackArtistNames = metadata.trackArtistNames; | ||
/// String? albumName = metadata.albumName; | ||
/// String? albumArtistName = metadata.albumArtistName; | ||
/// int? trackNumber = metadata.trackNumber; | ||
/// int? albumLength = metadata.albumLength; | ||
/// int? year = metadata.year; | ||
/// String? genre = metadata.genre; | ||
/// String? authorName = metadata.authorName; | ||
/// String? writerName = metadata.writerName; | ||
/// int? discNumber = metadata.discNumber; | ||
/// String? mimeType = metadata.mimeType; | ||
/// int? trackDuration = metadata.trackDuration; | ||
/// int? bitrate = metadata.bitrate; | ||
/// Uint8List? albumArt = metadata.albumArt; | ||
/// ``` | ||
/// | ||
library flutter_media_metadata; | ||
|
||
export 'package:flutter_media_metadata/src/flutter_media_metadata_native.dart' | ||
if (dart.library.html) 'package:flutter_media_metadata/src/flutter_media_metadata_web.dart'; | ||
export 'package:flutter_media_metadata/src/models/metadata.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/// This file is a part of flutter_media_metadata (https://github.com/alexmercerind/flutter_media_metadata). | ||
/// | ||
/// Copyright (c) 2021-2022, Hitesh Kumar Saini <[email protected]>. | ||
/// All rights reserved. | ||
/// Use of this source code is governed by MIT license that can be found in the LICENSE file. | ||
import 'dart:io'; | ||
import 'package:flutter/services.dart'; | ||
|
||
import 'package:flutter_media_metadata/src/models/metadata.dart'; | ||
|
||
/// ## MetadataRetriever | ||
/// | ||
/// Use [MetadataRetriever.fromFile] to extract [Metadata] from a media file. | ||
/// | ||
/// ```dart | ||
/// final metadata = MetadataRetriever.fromFile(file); | ||
/// String? trackName = metadata.trackName; | ||
/// List<String>? trackArtistNames = metadata.trackArtistNames; | ||
/// String? albumName = metadata.albumName; | ||
/// String? albumArtistName = metadata.albumArtistName; | ||
/// int? trackNumber = metadata.trackNumber; | ||
/// int? albumLength = metadata.albumLength; | ||
/// int? year = metadata.year; | ||
/// String? genre = metadata.genre; | ||
/// String? authorName = metadata.authorName; | ||
/// String? writerName = metadata.writerName; | ||
/// int? discNumber = metadata.discNumber; | ||
/// String? mimeType = metadata.mimeType; | ||
/// int? trackDuration = metadata.trackDuration; | ||
/// int? bitrate = metadata.bitrate; | ||
/// Uint8List? albumArt = metadata.albumArt; | ||
/// ``` | ||
/// | ||
class MetadataRetriever { | ||
/// Extracts [Metadata] from a [File]. Works on Windows, Linux, macOS, Android & iOS. | ||
static Future<Metadata> fromFile(File file) async { | ||
var metadata = await _kChannel.invokeMethod( | ||
'MetadataRetriever', | ||
{ | ||
'filePath': file.path, | ||
}, | ||
); | ||
metadata['filePath'] = file.path; | ||
return Metadata.fromJson(metadata); | ||
} | ||
|
||
/// Extracts [Metadata] from [Uint8List]. Works only on Web. | ||
static Future<Metadata> fromBytes(dynamic _) async { | ||
throw UnimplementedError( | ||
'[MetadataRetriever.fromBytes] is not supported on ${Platform.operatingSystem}. This method is only available for web. Use [MetadataRetriever.fromFile] instead.', | ||
); | ||
} | ||
} | ||
|
||
var _kChannel = const MethodChannel('flutter_media_metadata'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
/// This file is a part of flutter_media_metadata (https://github.com/alexmercerind/flutter_media_metadata). | ||
/// | ||
/// Copyright (c) 2021-2022, Hitesh Kumar Saini <[email protected]>. | ||
/// All rights reserved. | ||
/// Use of this source code is governed by MIT license that can be found in the LICENSE file. | ||
// ignore_for_file: missing_js_lib_annotation | ||
|
||
import 'dart:async'; | ||
import 'dart:convert'; | ||
import 'dart:typed_data'; | ||
import 'package:js/js.dart'; | ||
import 'package:flutter/services.dart'; | ||
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; | ||
|
||
import 'package:flutter_media_metadata/src/models/metadata.dart'; | ||
|
||
/// ## MetadataRetriever | ||
/// | ||
/// Use [MetadataRetriever.fromBytes] to extract [Metadata] from bytes of media file. | ||
/// | ||
/// ```dart | ||
/// final metadata = MetadataRetriever.fromBytes(byteData); | ||
/// String? trackName = metadata.trackName; | ||
/// List<String>? trackArtistNames = metadata.trackArtistNames; | ||
/// String? albumName = metadata.albumName; | ||
/// String? albumArtistName = metadata.albumArtistName; | ||
/// int? trackNumber = metadata.trackNumber; | ||
/// int? albumLength = metadata.albumLength; | ||
/// int? year = metadata.year; | ||
/// String? genre = metadata.genre; | ||
/// String? authorName = metadata.authorName; | ||
/// String? writerName = metadata.writerName; | ||
/// int? discNumber = metadata.discNumber; | ||
/// String? mimeType = metadata.mimeType; | ||
/// int? trackDuration = metadata.trackDuration; | ||
/// int? bitrate = metadata.bitrate; | ||
/// Uint8List? albumArt = metadata.albumArt; | ||
/// ``` | ||
/// | ||
class MetadataRetriever { | ||
static void registerWith(Registrar registrar) { | ||
final MethodChannel channel = MethodChannel( | ||
'flutter_media_metadata', | ||
const StandardMethodCodec(), | ||
registrar, | ||
); | ||
final pluginInstance = MetadataRetriever(); | ||
channel.setMethodCallHandler(pluginInstance.handleMethodCall); | ||
} | ||
|
||
Future<dynamic> handleMethodCall(MethodCall call) => throw PlatformException( | ||
code: 'Unimplemented', | ||
details: | ||
'flutter_media_metadata for web doesn\'t implement \'${call.method}\'', | ||
); | ||
|
||
/// Extracts [Metadata] from a [File]. Works on Windows, Linux, macOS, Android & iOS. | ||
static Future<Metadata> fromFile(dynamic _) async { | ||
throw UnimplementedError( | ||
'[MetadataRetriever.fromFile] is not supported on web. This method is only available for Windows, Linux, macOS, Android or iOS. Use [MetadataRetriever.fromBytes] instead.', | ||
); | ||
} | ||
|
||
/// Extracts [Metadata] from [Uint8List]. Works only on Web. | ||
static Future<Metadata> fromBytes(Uint8List bytes) { | ||
final completer = Completer<Metadata>(); | ||
MediaInfo( | ||
_Opts( | ||
chunkSize: 256 * 1024, | ||
coverData: true, | ||
format: 'JSON', | ||
), | ||
allowInterop( | ||
(mediainfo) { | ||
mediainfo | ||
.analyzeData( | ||
allowInterop(() => bytes.length), | ||
allowInterop( | ||
(chunkSize, offset) => _Promise( | ||
allowInterop( | ||
(resolve, reject) { | ||
resolve( | ||
bytes.sublist( | ||
offset, | ||
offset + chunkSize, | ||
), | ||
); | ||
}, | ||
), | ||
), | ||
), | ||
) | ||
.then( | ||
allowInterop( | ||
(result) { | ||
var rawMetadataJson = jsonDecode(result)['media']['track']; | ||
bool isFound = false; | ||
for (final data in rawMetadataJson) { | ||
if (data['@type'] == 'General') { | ||
isFound = true; | ||
rawMetadataJson = data; | ||
break; | ||
} | ||
} | ||
if (!isFound) { | ||
throw Exception(); | ||
} | ||
final metadata = <String, dynamic>{ | ||
'metadata': {}, | ||
'albumArt': base64Decode(rawMetadataJson['Cover_Data']), | ||
'filePath': null, | ||
}; | ||
_kMetadataKeys.forEach((key, value) { | ||
metadata['metadata'][key] = rawMetadataJson[value]; | ||
}); | ||
completer.complete(Metadata.fromJson(metadata)); | ||
}, | ||
), | ||
allowInterop( | ||
() { | ||
completer.completeError(Exception()); | ||
}, | ||
), | ||
); | ||
}, | ||
), | ||
allowInterop( | ||
() { | ||
completer.completeError(Exception()); | ||
}, | ||
), | ||
); | ||
return completer.future; | ||
} | ||
} | ||
|
||
@JS('Promise') | ||
class _Promise<T> { | ||
external _Promise(void executor(void resolve(T result), Function reject)); | ||
external _Promise then(void onFulfilled(T result), [Function onRejected]); | ||
} | ||
|
||
@JS('MediaInfo') | ||
external String MediaInfo( | ||
Object opts, | ||
void Function(_MediaInfo) successCallback, | ||
void Function() erroCallback, | ||
); | ||
|
||
@JS() | ||
@anonymous | ||
class _Opts { | ||
external int chunkSize; | ||
external bool coverData; | ||
external String format; | ||
|
||
external factory _Opts({int chunkSize, bool coverData, String format}); | ||
} | ||
|
||
@JS() | ||
@anonymous | ||
class _MediaInfo { | ||
external _Promise<String> analyzeData(int Function() getSize, | ||
_Promise<Uint8List> promise(int chunkSize, int offset)); | ||
|
||
external factory _MediaInfo(); | ||
} | ||
|
||
const _kMetadataKeys = <String, String>{ | ||
"trackName": "Track", | ||
"trackArtistNames": "Performer", | ||
"albumName": "Album", | ||
"albumArtistName": "Album_Performer", | ||
"trackNumber": "Track_Position", | ||
"albumLength": "Track_Position_Total", | ||
"year": "Recorded_Date", | ||
"genre": "Genre", | ||
"writerName": "WrittenBy", | ||
"trackDuration": "Duration", | ||
"bitrate": "OverallBitRate", | ||
}; |
Oops, something went wrong.