├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── ChristianLyricsPlugin.h │ ├── SwiftChristianLyricsPlugin.swift │ └── ChristianLyricsPlugin.m ├── .gitignore └── christian_lyrics.podspec ├── android ├── settings.gradle ├── gradle.properties ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── christian │ │ └── unity │ │ └── christian_lyrics │ │ └── ChristianLyricsPlugin.kt ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── build.gradle ├── .idea ├── .gitignore ├── vcs.xml ├── misc.xml ├── libraries │ ├── Flutter_Plugins.xml │ └── Dart_SDK.xml ├── modules.xml ├── runConfigurations.xml └── christian_lyrics.iml ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── build │ │ └── Pods.build │ │ │ └── Release-iphonesimulator │ │ │ ├── FMDB.build │ │ │ └── dgph │ │ │ ├── Flutter.build │ │ │ └── dgph │ │ │ ├── sqflite.build │ │ │ └── dgph │ │ │ ├── Pods-Runner.build │ │ │ └── dgph │ │ │ ├── just_audio.build │ │ │ └── dgph │ │ │ ├── audio_service.build │ │ │ └── dgph │ │ │ ├── audio_session.build │ │ │ └── dgph │ │ │ ├── christian_lyrics.build │ │ │ └── dgph │ │ │ └── path_provider_ios.build │ │ │ └── dgph │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ ├── Podfile │ └── Podfile.lock ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── christian │ │ │ │ │ │ └── unity │ │ │ │ │ │ └── christian_lyrics_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── assets │ └── OHolyNight_Christmas2016.mp3 ├── .metadata ├── README.md ├── .gitignore ├── analysis_options.yaml ├── pubspec.yaml ├── lib │ ├── common.dart │ └── main.dart └── pubspec.lock ├── screenshot_01.png ├── analysis_options.yaml ├── CHANGELOG.md ├── README.md ├── christian_lyrics.iml ├── LICENSE ├── .gitignore ├── pubspec.yaml ├── pubspec.lock └── lib ├── christian_lyrics.dart ├── color_map.dart ├── srt_parser.dart └── lyric.dart /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'christian_lyrics' 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/screenshot_01.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /ios/Classes/ChristianLyricsPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ChristianLyricsPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /example/assets/OHolyNight_Christmas2016.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/assets/OHolyNight_Christmas2016.mp3 -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/FMDB.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/sqflite.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/just_audio.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/audio_service.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/audio_session.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/christian_lyrics.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /example/ios/build/Pods.build/Release-iphonesimulator/path_provider_ios.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47/VolumesDataGithubchristian_lyricsexampleiosPods -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diepnghitinh/christian_lyrics/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.4 2 | 3 | * Performance lyric 4 | 5 | ## 0.0.3 6 | 7 | * Check lyric content is null 8 | 9 | ## 0.0.2 10 | 11 | * Publish github 12 | 13 | ## 0.0.1 14 | 15 | * Lyrics animation for flutter 2.x 16 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/christian/unity/christian_lyrics_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.christian.unity.christian_lyrics_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: ffb2ecea5223acdd139a5039be2f9c796962833d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Classes/SwiftChristianLyricsPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftChristianLyricsPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "christian_lyrics", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftChristianLyricsPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | result("iOS " + UIDevice.current.systemVersion) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # christian_lyrics_example 2 | 3 | Demonstrates how to use the christian_lyrics plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # christian_lyrics 2 | 3 | Flutter plugin that allows you build lyrics srt type of song. 4 | 5 | ## Getting Started 6 | 7 | # srt file format 8 | ``` 9 | 1 10 | 00:05:00,400 --> 00:05:15,300 11 | This is an example of 12 | a subtitle. 13 | 14 | 2 15 | 00:05:16,400 --> 00:05:25,300 16 | This is an example of 17 | a subtitle - 2nd subtitle. 18 | ``` 19 | 20 | # How to use 21 | ``` 22 | final christianLyrics = ChristianLyrics(); 23 | christianLyrics.setLyricContent(lyricText); 24 | 25 | //Build widget 26 | christianLyrics.getLyric(context, isPlaying: true); 27 | ``` 28 | 29 | # Demo 30 | ![Demo](https://github.com/diepnghitinh/christian_lyrics/raw/main/screenshot_01.png) -------------------------------------------------------------------------------- /ios/Classes/ChristianLyricsPlugin.m: -------------------------------------------------------------------------------- 1 | #import "ChristianLyricsPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "christian_lyrics-Swift.h" 9 | #endif 10 | 11 | @implementation ChristianLyricsPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftChristianLyricsPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /ios/christian_lyrics.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint christian_lyrics.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'christian_lyrics' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter project.' 9 | s.description = <<-DESC 10 | A new Flutter project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.christian.unity.christian_lyrics' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.3.50' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.1.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 30 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | minSdkVersion 16 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 50 | } 51 | -------------------------------------------------------------------------------- /christian_lyrics.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/christian/unity/christian_lyrics/ChristianLyricsPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.christian.unity.christian_lyrics 2 | 3 | import androidx.annotation.NonNull 4 | 5 | import io.flutter.embedding.engine.plugins.FlutterPlugin 6 | import io.flutter.plugin.common.MethodCall 7 | import io.flutter.plugin.common.MethodChannel 8 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 9 | import io.flutter.plugin.common.MethodChannel.Result 10 | 11 | /** ChristianLyricsPlugin */ 12 | class ChristianLyricsPlugin: FlutterPlugin, MethodCallHandler { 13 | /// The MethodChannel that will the communication between Flutter and native Android 14 | /// 15 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 16 | /// when the Flutter Engine is detached from the Activity 17 | private lateinit var channel : MethodChannel 18 | 19 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 20 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "christian_lyrics") 21 | channel.setMethodCallHandler(this) 22 | } 23 | 24 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 25 | if (call.method == "getPlatformVersion") { 26 | result.success("Android ${android.os.Build.VERSION.RELEASE}") 27 | } else { 28 | result.notImplemented() 29 | } 30 | } 31 | 32 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 33 | channel.setMethodCallHandler(null) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | christian_lyrics_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | 17 | # The .vscode folder contains launch configuration and tasks you configure in 18 | # VS Code which you may wish to be included in version control, so this line 19 | # is commented out by default. 20 | #.vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | /build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - audio_service (0.0.1): 3 | - Flutter 4 | - audio_session (0.0.1): 5 | - Flutter 6 | - christian_lyrics (0.0.1): 7 | - Flutter 8 | - Flutter (1.0.0) 9 | - FMDB (2.7.5): 10 | - FMDB/standard (= 2.7.5) 11 | - FMDB/standard (2.7.5) 12 | - just_audio (0.0.1): 13 | - Flutter 14 | - path_provider_ios (0.0.1): 15 | - Flutter 16 | - sqflite (0.0.2): 17 | - Flutter 18 | - FMDB (>= 2.7.5) 19 | 20 | DEPENDENCIES: 21 | - audio_service (from `.symlinks/plugins/audio_service/ios`) 22 | - audio_session (from `.symlinks/plugins/audio_session/ios`) 23 | - christian_lyrics (from `.symlinks/plugins/christian_lyrics/ios`) 24 | - Flutter (from `Flutter`) 25 | - just_audio (from `.symlinks/plugins/just_audio/ios`) 26 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 27 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 28 | 29 | SPEC REPOS: 30 | trunk: 31 | - FMDB 32 | 33 | EXTERNAL SOURCES: 34 | audio_service: 35 | :path: ".symlinks/plugins/audio_service/ios" 36 | audio_session: 37 | :path: ".symlinks/plugins/audio_session/ios" 38 | christian_lyrics: 39 | :path: ".symlinks/plugins/christian_lyrics/ios" 40 | Flutter: 41 | :path: Flutter 42 | just_audio: 43 | :path: ".symlinks/plugins/just_audio/ios" 44 | path_provider_ios: 45 | :path: ".symlinks/plugins/path_provider_ios/ios" 46 | sqflite: 47 | :path: ".symlinks/plugins/sqflite/ios" 48 | 49 | SPEC CHECKSUMS: 50 | audio_service: f509d65da41b9521a61f1c404dd58651f265a567 51 | audio_session: 4f3e461722055d21515cf3261b64c973c062f345 52 | christian_lyrics: ba1cd12d0132f266b9caafeb7020f8898bb59824 53 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 54 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 55 | just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa 56 | path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 57 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 58 | 59 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 60 | 61 | COCOAPODS: 1.10.1 62 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.christian.unity.christian_lyrics_example" 47 | minSdkVersion 16 48 | targetSdkVersion 30 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: christian_lyrics 2 | description: Flutter plugin for lyrics srt type of song. 3 | version: 0.0.4 4 | homepage: https://github.com/diepnghitinh/christian_lyrics 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.20.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | csslib: ^0.17.0 15 | meta: ^1.3.0 16 | async: ^2.8.2 17 | 18 | dev_dependencies: 19 | flutter_lints: ^1.0.0 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter. 25 | flutter: 26 | # This section identifies this Flutter project as a plugin project. 27 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily 28 | # be modified. They are used by the tooling to maintain consistency when 29 | # adding or updating assets for this project. 30 | plugin: 31 | platforms: 32 | android: 33 | package: com.christian.unity.christian_lyrics 34 | pluginClass: ChristianLyricsPlugin 35 | ios: 36 | pluginClass: ChristianLyricsPlugin 37 | 38 | # To add assets to your plugin package, add an assets section, like this: 39 | # assets: 40 | # - images/a_dot_burr.jpeg 41 | # - images/a_dot_ham.jpeg 42 | # 43 | # For details regarding assets in packages, see 44 | # https://flutter.dev/assets-and-images/#from-packages 45 | # 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.dev/assets-and-images/#resolution-aware. 48 | 49 | # To add custom fonts to your plugin package, add a fonts section here, 50 | # in this "flutter" section. Each entry in this list should have a 51 | # "family" key with the font family name, and a "fonts" key with a 52 | # list giving the asset and other descriptors for the font. For 53 | # example: 54 | # fonts: 55 | # - family: Schyler 56 | # fonts: 57 | # - asset: fonts/Schyler-Regular.ttf 58 | # - asset: fonts/Schyler-Italic.ttf 59 | # style: italic 60 | # - family: Trajan Pro 61 | # fonts: 62 | # - asset: fonts/TrajanPro.ttf 63 | # - asset: fonts/TrajanPro_Bold.ttf 64 | # weight: 700 65 | # 66 | # For details regarding fonts in packages, see 67 | # https://flutter.dev/custom-fonts/#from-packages 68 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: "direct main" 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | characters: 12 | dependency: transitive 13 | description: 14 | name: characters 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.1.0" 18 | collection: 19 | dependency: transitive 20 | description: 21 | name: collection 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.15.0" 25 | csslib: 26 | dependency: "direct main" 27 | description: 28 | name: csslib 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "0.17.1" 32 | flutter: 33 | dependency: "direct main" 34 | description: flutter 35 | source: sdk 36 | version: "0.0.0" 37 | flutter_lints: 38 | dependency: "direct dev" 39 | description: 40 | name: flutter_lints 41 | url: "https://pub.dartlang.org" 42 | source: hosted 43 | version: "1.0.4" 44 | lints: 45 | dependency: transitive 46 | description: 47 | name: lints 48 | url: "https://pub.dartlang.org" 49 | source: hosted 50 | version: "1.0.1" 51 | meta: 52 | dependency: "direct main" 53 | description: 54 | name: meta 55 | url: "https://pub.dartlang.org" 56 | source: hosted 57 | version: "1.7.0" 58 | path: 59 | dependency: transitive 60 | description: 61 | name: path 62 | url: "https://pub.dartlang.org" 63 | source: hosted 64 | version: "1.8.0" 65 | sky_engine: 66 | dependency: transitive 67 | description: flutter 68 | source: sdk 69 | version: "0.0.99" 70 | source_span: 71 | dependency: transitive 72 | description: 73 | name: source_span 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.8.1" 77 | term_glyph: 78 | dependency: transitive 79 | description: 80 | name: term_glyph 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.2.0" 84 | typed_data: 85 | dependency: transitive 86 | description: 87 | name: typed_data 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.3.0" 91 | vector_math: 92 | dependency: transitive 93 | description: 94 | name: vector_math 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "2.1.0" 98 | sdks: 99 | dart: ">=2.12.0 <3.0.0" 100 | flutter: ">=1.20.0" 101 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: christian_lyrics_example 2 | description: Demonstrates how to use the christian_lyrics plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | # Dependencies specify other packages that your package needs in order to work. 12 | # To automatically upgrade your package dependencies to the latest versions 13 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 14 | # dependencies can be manually updated by changing the version numbers below to 15 | # the latest version available on pub.dev. To see which dependencies have newer 16 | # versions available, run `flutter pub outdated`. 17 | dependencies: 18 | flutter: 19 | sdk: flutter 20 | 21 | christian_lyrics: 22 | # When depending on this package from a real application you should use: 23 | # christian_lyrics: ^x.y.z 24 | # See https://dart.dev/tools/pub/dependencies#version-constraints 25 | # The example app is bundled with the plugin so we use a path dependency on 26 | # the parent directory to use the current plugin's version. 27 | path: ../ 28 | 29 | audio_session: ^0.1.6 30 | audio_service: ^0.18.0 31 | just_audio: ^0.9.11 32 | 33 | # The following adds the Cupertino Icons font to your application. 34 | # Use with the CupertinoIcons class for iOS style icons. 35 | cupertino_icons: ^1.0.2 36 | 37 | dev_dependencies: 38 | 39 | # The "flutter_lints" package below contains a set of recommended lints to 40 | # encourage good coding practices. The lint set provided by the package is 41 | # activated in the `analysis_options.yaml` file located at the root of your 42 | # package. See that file for information about deactivating specific lint 43 | # rules and activating additional ones. 44 | flutter_lints: ^1.0.0 45 | 46 | # For information on the generic Dart part of this file, see the 47 | # following page: https://dart.dev/tools/pub/pubspec 48 | 49 | # The following section is specific to Flutter. 50 | flutter: 51 | 52 | assets: 53 | - assets/ 54 | 55 | # The following line ensures that the Material Icons font is 56 | # included with your application, so that you can use the icons in 57 | # the material Icons class. 58 | uses-material-design: true 59 | 60 | # To add assets to your application, add an assets section, like this: 61 | # assets: 62 | # - images/a_dot_burr.jpeg 63 | # - images/a_dot_ham.jpeg 64 | 65 | # An image asset can refer to one or more resolution-specific "variants", see 66 | # https://flutter.dev/assets-and-images/#resolution-aware. 67 | 68 | # For details regarding adding assets from package dependencies, see 69 | # https://flutter.dev/assets-and-images/#from-packages 70 | 71 | # To add custom fonts to your application, add a fonts section here, 72 | # in this "flutter" section. Each entry in this list should have a 73 | # "family" key with the font family name, and a "fonts" key with a 74 | # list giving the asset and other descriptors for the font. For 75 | # example: 76 | # fonts: 77 | # - family: Schyler 78 | # fonts: 79 | # - asset: fonts/Schyler-Regular.ttf 80 | # - asset: fonts/Schyler-Italic.ttf 81 | # style: italic 82 | # - family: Trajan Pro 83 | # fonts: 84 | # - asset: fonts/TrajanPro.ttf 85 | # - asset: fonts/TrajanPro_Bold.ttf 86 | # weight: 700 87 | # 88 | # For details regarding fonts from package dependencies, 89 | # see https://flutter.dev/custom-fonts/#from-packages 90 | -------------------------------------------------------------------------------- /lib/christian_lyrics.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'dart:ui' as ui; 7 | 8 | import 'lyric.dart'; 9 | 10 | class ChristianLyrics { 11 | 12 | StreamController positionWithOffsetController = StreamController.broadcast(); 13 | int lastPositionUpdateTime = 0; 14 | int positionWithOffset = 0; 15 | int lastPositionWithOffset = 0; 16 | Widget? cacheLyricWidget; 17 | 18 | PlayingLyric? playingLyric = PlayingLyric(); 19 | 20 | ChristianLyrics() { 21 | resetLyric(); 22 | } 23 | 24 | void resetLyric() { 25 | lastPositionUpdateTime = DateTime.now().millisecondsSinceEpoch; 26 | lastPositionWithOffset = positionWithOffset; 27 | //print("a: ${lastPositionUpdateTime} - ${lastPositionWithOffset}"); 28 | } 29 | 30 | void setLyricContent(String lyricContent) { 31 | positionWithOffset = 0; 32 | this.playingLyric!.setLyric(lyric: lyricContent); 33 | } 34 | 35 | void setPositionWithOffset({int position=0, int duration=1}) { 36 | positionWithOffset = position; 37 | positionWithOffsetController.add(positionWithOffset); 38 | } 39 | 40 | Widget getLyric(BuildContext context, {bool isPlaying = false}) { 41 | 42 | TextStyle style = Theme.of(context).textTheme.bodyText1!.copyWith(height: 1.5, fontSize: 20, color: Colors.white); 43 | 44 | if (this.playingLyric!.hasLyric) { 45 | return LayoutBuilder(builder: (context, constraints) { 46 | final normalStyle = style.copyWith(color: style.color!.withOpacity(0.7)); 47 | return ShaderMask( 48 | shaderCallback: (rect) { 49 | return ui.Gradient.linear(Offset(rect.width / 2, 0), Offset(rect.width / 2, constraints.maxHeight), [ 50 | const Color(0x00FFFFFF), 51 | style.color!, 52 | style.color!, 53 | const Color(0x00FFFFFF), 54 | ], [ 55 | 0.0, 56 | 0.15, 57 | 0.85, 58 | 1 59 | ]); 60 | }, 61 | // child: Padding( 62 | // padding: EdgeInsets.symmetric(horizontal: 20), 63 | // child: StreamBuilder( 64 | // stream: positionWithOffsetController.stream, 65 | // builder: (BuildContext context, AsyncSnapshot snapshot) { 66 | // final result = snapshot.data ?? 0; 67 | // 68 | // //print('position: ${result} - ${positionWithOffset} - isPlaying: ${isPlaying}'); 69 | // 70 | // return Lyric( 71 | // lyric: playingLyric!.lyric!, 72 | // lyricLineStyle: normalStyle, 73 | // highlight: style.color!, 74 | // position: result, 75 | // streamPosition: positionWithOffsetController, 76 | // // cacheLyricWidget: (Widget wg) { 77 | // // cacheLyricWidget ??= wg; 78 | // // return cacheLyricWidget; 79 | // // }, 80 | // onTap: () { 81 | // }, 82 | // size: Size(constraints.maxWidth, constraints.maxHeight == double.infinity ? 0 : constraints.maxHeight), 83 | // playing: isPlaying, 84 | // ); 85 | // } 86 | // ) 87 | // ), 88 | child: Padding( 89 | padding: EdgeInsets.symmetric(horizontal: 20), 90 | child: Lyric( 91 | lyric: playingLyric!.lyric!, 92 | lyricLineStyle: normalStyle, 93 | highlight: style.color!, 94 | streamPosition: positionWithOffsetController, 95 | onTap: () { 96 | }, 97 | size: Size(constraints.maxWidth, constraints.maxHeight == double.infinity ? 0 : constraints.maxHeight), 98 | playing: isPlaying, 99 | ) 100 | ), 101 | ); 102 | }); 103 | } else { 104 | return Container( 105 | child: Center( 106 | child: Text(playingLyric!.message, style: style), 107 | ), 108 | ); 109 | } 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /example/lib/common.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class SeekBar extends StatefulWidget { 5 | final Duration duration; 6 | final Duration position; 7 | final Duration bufferedPosition; 8 | final ValueChanged? onChanged; 9 | final ValueChanged? onChangeEnd; 10 | 11 | SeekBar({ 12 | required this.duration, 13 | required this.position, 14 | required this.bufferedPosition, 15 | this.onChanged, 16 | this.onChangeEnd, 17 | }); 18 | 19 | @override 20 | _SeekBarState createState() => _SeekBarState(); 21 | } 22 | 23 | class _SeekBarState extends State { 24 | double? _dragValue; 25 | late SliderThemeData _sliderThemeData; 26 | 27 | @override 28 | void didChangeDependencies() { 29 | super.didChangeDependencies(); 30 | 31 | _sliderThemeData = SliderTheme.of(context).copyWith( 32 | trackHeight: 2.0, 33 | ); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Stack( 39 | children: [ 40 | SliderTheme( 41 | data: _sliderThemeData.copyWith( 42 | thumbShape: HiddenThumbComponentShape(), 43 | activeTrackColor: Colors.blue.shade100, 44 | inactiveTrackColor: Colors.grey.shade300, 45 | ), 46 | child: ExcludeSemantics( 47 | child: Slider( 48 | min: 0.0, 49 | max: widget.duration.inMilliseconds.toDouble(), 50 | value: min(widget.bufferedPosition.inMilliseconds.toDouble(), 51 | widget.duration.inMilliseconds.toDouble()), 52 | onChanged: (value) { 53 | setState(() { 54 | _dragValue = value; 55 | }); 56 | if (widget.onChanged != null) { 57 | widget.onChanged!(Duration(milliseconds: value.round())); 58 | } 59 | }, 60 | onChangeEnd: (value) { 61 | if (widget.onChangeEnd != null) { 62 | widget.onChangeEnd!(Duration(milliseconds: value.round())); 63 | } 64 | _dragValue = null; 65 | }, 66 | ), 67 | ), 68 | ), 69 | SliderTheme( 70 | data: _sliderThemeData.copyWith( 71 | inactiveTrackColor: Colors.transparent, 72 | ), 73 | child: Slider( 74 | min: 0.0, 75 | max: widget.duration.inMilliseconds.toDouble(), 76 | value: min(_dragValue ?? widget.position.inMilliseconds.toDouble(), 77 | widget.duration.inMilliseconds.toDouble()), 78 | onChanged: (value) { 79 | setState(() { 80 | _dragValue = value; 81 | }); 82 | if (widget.onChanged != null) { 83 | widget.onChanged!(Duration(milliseconds: value.round())); 84 | } 85 | }, 86 | onChangeEnd: (value) { 87 | if (widget.onChangeEnd != null) { 88 | widget.onChangeEnd!(Duration(milliseconds: value.round())); 89 | } 90 | _dragValue = null; 91 | }, 92 | ), 93 | ), 94 | Positioned( 95 | left: 16.0, 96 | bottom: 0.0, 97 | child: Text( 98 | RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$') 99 | .firstMatch("$_position") 100 | ?.group(1) ?? 101 | '$_position', 102 | style: Theme.of(context).textTheme.caption), 103 | ), 104 | Positioned( 105 | right: 16.0, 106 | bottom: 0.0, 107 | child: Text( 108 | RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$') 109 | .firstMatch("$_remaining") 110 | ?.group(1) ?? 111 | '$_remaining', 112 | style: Theme.of(context).textTheme.caption), 113 | ), 114 | ], 115 | ); 116 | } 117 | 118 | Duration get _position => widget.position; 119 | Duration get _remaining => widget.duration - widget.position; 120 | } 121 | 122 | class HiddenThumbComponentShape extends SliderComponentShape { 123 | @override 124 | Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero; 125 | 126 | @override 127 | void paint( 128 | PaintingContext context, 129 | Offset center, { 130 | required Animation activationAnimation, 131 | required Animation enableAnimation, 132 | required bool isDiscrete, 133 | required TextPainter labelPainter, 134 | required RenderBox parentBox, 135 | required SliderThemeData sliderTheme, 136 | required TextDirection textDirection, 137 | required double value, 138 | required double textScaleFactor, 139 | required Size sizeWithOverflow, 140 | }) {} 141 | } 142 | 143 | class PositionData { 144 | final Duration position; 145 | final Duration bufferedPosition; 146 | final Duration duration; 147 | 148 | PositionData(this.position, this.bufferedPosition, this.duration); 149 | } 150 | 151 | void showSliderDialog({ 152 | required BuildContext context, 153 | required String title, 154 | required int divisions, 155 | required double min, 156 | required double max, 157 | String valueSuffix = '', 158 | // TODO: Replace these two by ValueStream. 159 | required double value, 160 | required Stream stream, 161 | required ValueChanged onChanged, 162 | }) { 163 | showDialog( 164 | context: context, 165 | builder: (context) => AlertDialog( 166 | title: Text(title, textAlign: TextAlign.center), 167 | content: StreamBuilder( 168 | stream: stream, 169 | builder: (context, snapshot) => Container( 170 | height: 100.0, 171 | child: Column( 172 | children: [ 173 | Text('${snapshot.data?.toStringAsFixed(1)}$valueSuffix', 174 | style: TextStyle( 175 | fontFamily: 'Fixed', 176 | fontWeight: FontWeight.bold, 177 | fontSize: 24.0)), 178 | Slider( 179 | divisions: divisions, 180 | min: min, 181 | max: max, 182 | value: snapshot.data ?? value, 183 | onChanged: onChanged, 184 | ), 185 | ], 186 | ), 187 | ), 188 | ), 189 | ), 190 | ); 191 | } -------------------------------------------------------------------------------- /lib/color_map.dart: -------------------------------------------------------------------------------- 1 | import 'package:csslib/parser.dart'; 2 | 3 | Map colorMap = { 4 | 'transparent': const Color.hex("00ffffff"), 5 | 'aliceBlue': const Color.hex("0f08ff"), 6 | 'antiqueWhite': const Color.hex("0faebd7"), 7 | 'aqua': const Color.hex("00ffff"), 8 | 'aquaMarine': const Color.hex("7fffd4"), 9 | 'azure': const Color.hex("f0ffff"), 10 | 'beige': const Color.hex("f5f5dc"), 11 | 'bisque': const Color.hex("ffe4c4"), 12 | 'black': const Color.hex("000000"), 13 | 'blanchedAlmond': const Color.hex("ffebcd"), 14 | 'blue': const Color.hex("0000ff"), 15 | 'blueViolet': const Color.hex("8a2be2"), 16 | 'brown': const Color.hex("a52a2a"), 17 | 'burlyWood': const Color.hex("deb887"), 18 | 'cadetBlue': const Color.hex("5f9ea0"), 19 | 'chartreuse': const Color.hex("7fff00"), 20 | 'chocolate': const Color.hex("d2691e"), 21 | 'coral': const Color.hex("ff7f50"), 22 | 'cornFlowerBlue': const Color.hex("6495ed"), 23 | 'cornSilk': const Color.hex("fff8dc"), 24 | 'crimson': const Color.hex("dc143c"), 25 | 'cyan': const Color.hex("00ffff"), 26 | 'darkBlue': const Color.hex("00008b"), 27 | 'darkCyan': const Color.hex("008b8b"), 28 | 'darkGoldenRod': const Color.hex("b8860b"), 29 | 'darkGray': const Color.hex("a9a9a9"), 30 | 'darkGreen': const Color.hex("006400"), 31 | 'darkGrey': const Color.hex("a9a9a9"), 32 | 'darkKhaki': const Color.hex("bdb76b"), 33 | 'darkMagenta': const Color.hex("8b008b"), 34 | 'darkOliveGreen': const Color.hex("556b2f"), 35 | 'darkOrange': const Color.hex("ff8c00"), 36 | 'darkOrchid': const Color.hex("9932cc"), 37 | 'darkRed': const Color.hex("8b0000"), 38 | 'darkSalmon': const Color.hex("e9967a"), 39 | 'darkSeaGreen': const Color.hex("8fbc8f"), 40 | 'darkSlateBlue': const Color.hex("483d8b"), 41 | 'darkSlateGray': const Color.hex("2f4f4f"), 42 | 'darkSlateGrey': const Color.hex("2f4f4f"), 43 | 'darkTurquoise': const Color.hex("00ced1"), 44 | 'darkViolet': const Color.hex("9400d3"), 45 | 'deepPink': const Color.hex("ff1493"), 46 | 'deepSkyBlue': const Color.hex("00bfff"), 47 | 'dimGray': const Color.hex("696969"), 48 | 'dimGrey': const Color.hex("696969"), 49 | 'dodgerBlue': const Color.hex("1e90ff"), 50 | 'fireBrick': const Color.hex("b22222"), 51 | 'floralWhite': const Color.hex("fffaf0"), 52 | 'forestGreen': const Color.hex("228b22"), 53 | 'fuchsia': const Color.hex("ff00ff"), 54 | 'gainsboro': const Color.hex("dcdcdc"), 55 | 'ghostWhite': const Color.hex("f8f8ff"), 56 | 'gold': const Color.hex("ffd700"), 57 | 'goldenRod': const Color.hex("daa520"), 58 | 'gray': const Color.hex("808080"), 59 | 'green': const Color.hex("008000"), 60 | 'greenYellow': const Color.hex("adff2f"), 61 | 'grey': const Color.hex("808080"), 62 | 'honeydew': const Color.hex("f0fff0"), 63 | 'hotPink': const Color.hex("ff69b4"), 64 | 'indianRed': const Color.hex("cd5c5c"), 65 | 'indigo': const Color.hex("4b0082"), 66 | 'ivory': const Color.hex("fffff0"), 67 | 'khaki': const Color.hex("f0e68c"), 68 | 'lavender': const Color.hex("e6e6fa"), 69 | 'lavenderBlush': const Color.hex("fff0f5"), 70 | 'lawnGreen': const Color.hex("7cfc00"), 71 | 'lemonChiffon': const Color.hex("fffacd"), 72 | 'lightBlue': const Color.hex("add8e6"), 73 | 'lightCoral': const Color.hex("f08080"), 74 | 'lightCyan': const Color.hex("e0ffff"), 75 | 'lightGoldenRodYellow': const Color.hex("fafad2"), 76 | 'lightGray': const Color.hex("d3d3d3"), 77 | 'lightGreen': const Color.hex("90ee90"), 78 | 'lightGrey': const Color.hex("d3d3d3"), 79 | 'lightPink': const Color.hex("ffb6c1"), 80 | 'lightSalmon': const Color.hex("ffa07a"), 81 | 'lightSeaGreen': const Color.hex("20b2aa"), 82 | 'lightSkyBlue': const Color.hex("87cefa"), 83 | 'lightSlateGray': const Color.hex("778899"), 84 | 'lightSlateGrey': const Color.hex("778899"), 85 | 'lightSteelBlue': const Color.hex("b0c4de"), 86 | 'lightYellow': const Color.hex("ffffe0"), 87 | 'lime': const Color.hex("00ff00"), 88 | 'limeGreen': const Color.hex("32cd32"), 89 | 'linen': const Color.hex("faf0e6"), 90 | 'magenta': const Color.hex("ff00ff"), 91 | 'maroon': const Color.hex("800000"), 92 | 'mediumAquaMarine': const Color.hex("66cdaa"), 93 | 'mediumBlue': const Color.hex("0000cd"), 94 | 'mediumOrchid': const Color.hex("ba55d3"), 95 | 'mediumPurple': const Color.hex("9370db"), 96 | 'mediumSeaGreen': const Color.hex("3cb371"), 97 | 'mediumSlateBlue': const Color.hex("7b68ee"), 98 | 'mediumSpringGreen': const Color.hex("00fa9a"), 99 | 'mediumTurquoise': const Color.hex("48d1cc"), 100 | 'mediumVioletRed': const Color.hex("c71585"), 101 | 'midnightBlue': const Color.hex("191970"), 102 | 'mintCream': const Color.hex("f5fffa"), 103 | 'mistyRose': const Color.hex("ffe4e1"), 104 | 'moccasin': const Color.hex("ffe4b5"), 105 | 'navajoWhite': const Color.hex("ffdead"), 106 | 'navy': const Color.hex("000080"), 107 | 'oldLace': const Color.hex("fdf5e6"), 108 | 'olive': const Color.hex("808000"), 109 | 'oliveDrab': const Color.hex("6b8e23"), 110 | 'orange': const Color.hex("ffa500"), 111 | 'orangeRed': const Color.hex("ff4500"), 112 | 'orchid': const Color.hex("da70d6"), 113 | 'paleGoldenRod': const Color.hex("eee8aa"), 114 | 'paleGreen': const Color.hex("98fb98"), 115 | 'paleTurquoise': const Color.hex("afeeee"), 116 | 'paleVioletRed': const Color.hex("db7093"), 117 | 'papayaWhip': const Color.hex("ffefd5"), 118 | 'peachPuff': const Color.hex("ffdab9"), 119 | 'peru': const Color.hex("cd85ef"), 120 | 'pink': const Color.hex("ffc0cb"), 121 | 'plum': const Color.hex("dda0dd"), 122 | 'powderBlue': const Color.hex("b0e0e6"), 123 | 'purple': const Color.hex("800080"), 124 | 'red': const Color.hex("ff0000"), 125 | 'rosyBrown': const Color.hex("bc8f8f"), 126 | 'royalBlue': const Color.hex("4169e1"), 127 | 'saddleBrown': const Color.hex("8b4513"), 128 | 'salmon': const Color.hex("fa8072"), 129 | 'sandyBrown': const Color.hex("f4a460"), 130 | 'seaGreen': const Color.hex("2e8b57"), 131 | 'seashell': const Color.hex("fff5ee"), 132 | 'sienna': const Color.hex("a0522d"), 133 | 'silver': const Color.hex("c0c0c0"), 134 | 'skyBlue': const Color.hex("87ceeb"), 135 | 'slateBlue': const Color.hex("6a5acd"), 136 | 'slateGray': const Color.hex("708090"), 137 | 'slateGrey': const Color.hex("708090"), 138 | 'snow': const Color.hex("fffafa"), 139 | 'springGreen': const Color.hex("00ff7f"), 140 | 'steelBlue': const Color.hex("4682b4"), 141 | 'tan': const Color.hex("d2b48c"), 142 | 'teal': const Color.hex("008080"), 143 | 'thistle': const Color.hex("d8bfd8"), 144 | 'tomato': const Color.hex("ff6347"), 145 | 'turquoise': const Color.hex("40e0d0"), 146 | 'violet': const Color.hex("ee82ee"), 147 | 'wheat': const Color.hex("f5deb3"), 148 | 'white': const Color.hex("ffffff"), 149 | 'whiteSmoke': const Color.hex("f5f5f5"), 150 | 'yellow': const Color.hex("ffff00"), 151 | 'yellowGreen': const Color.hex("9acd32") 152 | }; -------------------------------------------------------------------------------- /lib/srt_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:csslib/parser.dart'; 2 | import 'package:meta/meta.dart'; 3 | import 'dart:convert' show LineSplitter; 4 | 5 | import 'color_map.dart'; 6 | 7 | /// formatting (partial compliance) : https://en.wikipedia.org/wiki/SubRip#Formatting 8 | class Range { 9 | Range(this.begin, this.end); 10 | 11 | int begin; 12 | int end; 13 | 14 | Duration get duration => Duration(milliseconds: end - begin); 15 | } 16 | 17 | class HtmlCode { 18 | bool i = false; 19 | bool u = false; 20 | bool b = false; 21 | Color fontColor = Color.black; 22 | } 23 | 24 | class Coordinates { 25 | Coordinates({this.x, this.y}); 26 | 27 | final int? x; 28 | final int? y; 29 | } 30 | 31 | class Subtitle { 32 | int? id; 33 | Range? range; 34 | List parsedLines = []; 35 | List rawLines = []; 36 | } 37 | 38 | class Line { 39 | Line(this.rawLine); 40 | 41 | final String rawLine; 42 | Coordinates? coordinates; 43 | 44 | // TODO(Arman):Either a whole line has code or subLines have or none 45 | List subLines = []; 46 | } 47 | 48 | class SubLine { 49 | SubLine({this.rawString}); 50 | 51 | HtmlCode htmlCode = HtmlCode(); 52 | String? rawString; 53 | } 54 | 55 | @visibleForTesting 56 | void parseHtml(Subtitle subtitle) { 57 | //https://regex101.com/r/LtkFNE/4 58 | final RegExp detectAll = RegExp( 59 | r'((<(b|i|u|(font color="((#([0-9a-fA-F]+))|((rgb|rgba)\(((\d{1,3}),(\d{1,3}),(\d{1,3})|(\d{1,3}),(\d{1,3}),(\d{1,3}),(0?\.[1-9]{1,2}|1))\))|([a-z]+))"))>)+)([^<|>|\/]+)((<\/(b|i|u|font)>)+)+|([^<|>|\/]+)'); 60 | 61 | final RegExp detectFont = RegExp(r'( 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | audio_service: 12 | dependency: "direct main" 13 | description: 14 | name: audio_service 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.18.1" 18 | audio_service_platform_interface: 19 | dependency: transitive 20 | description: 21 | name: audio_service_platform_interface 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "0.1.0" 25 | audio_service_web: 26 | dependency: transitive 27 | description: 28 | name: audio_service_web 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "0.1.1" 32 | audio_session: 33 | dependency: "direct main" 34 | description: 35 | name: audio_session 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.1.6+1" 39 | characters: 40 | dependency: transitive 41 | description: 42 | name: characters 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.3.1" 53 | christian_lyrics: 54 | dependency: "direct main" 55 | description: 56 | path: ".." 57 | relative: true 58 | source: path 59 | version: "0.0.3" 60 | clock: 61 | dependency: transitive 62 | description: 63 | name: clock 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.1.0" 67 | collection: 68 | dependency: transitive 69 | description: 70 | name: collection 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.15.0" 74 | crypto: 75 | dependency: transitive 76 | description: 77 | name: crypto 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "3.0.1" 81 | csslib: 82 | dependency: transitive 83 | description: 84 | name: csslib 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.17.1" 88 | cupertino_icons: 89 | dependency: "direct main" 90 | description: 91 | name: cupertino_icons 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.0.4" 95 | ffi: 96 | dependency: transitive 97 | description: 98 | name: ffi 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.1.2" 102 | file: 103 | dependency: transitive 104 | description: 105 | name: file 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "6.1.2" 109 | flutter: 110 | dependency: "direct main" 111 | description: flutter 112 | source: sdk 113 | version: "0.0.0" 114 | flutter_cache_manager: 115 | dependency: transitive 116 | description: 117 | name: flutter_cache_manager 118 | url: "https://pub.dartlang.org" 119 | source: hosted 120 | version: "3.3.0" 121 | flutter_lints: 122 | dependency: "direct dev" 123 | description: 124 | name: flutter_lints 125 | url: "https://pub.dartlang.org" 126 | source: hosted 127 | version: "1.0.4" 128 | flutter_web_plugins: 129 | dependency: transitive 130 | description: flutter 131 | source: sdk 132 | version: "0.0.0" 133 | http: 134 | dependency: transitive 135 | description: 136 | name: http 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "0.13.4" 140 | http_parser: 141 | dependency: transitive 142 | description: 143 | name: http_parser 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "4.0.0" 147 | js: 148 | dependency: transitive 149 | description: 150 | name: js 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.6.3" 154 | just_audio: 155 | dependency: "direct main" 156 | description: 157 | name: just_audio 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.9.18" 161 | just_audio_platform_interface: 162 | dependency: transitive 163 | description: 164 | name: just_audio_platform_interface 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "4.0.0" 168 | just_audio_web: 169 | dependency: transitive 170 | description: 171 | name: just_audio_web 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "0.4.2" 175 | lints: 176 | dependency: transitive 177 | description: 178 | name: lints 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "1.0.1" 182 | meta: 183 | dependency: transitive 184 | description: 185 | name: meta 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "1.7.0" 189 | path: 190 | dependency: transitive 191 | description: 192 | name: path 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "1.8.0" 196 | path_provider: 197 | dependency: transitive 198 | description: 199 | name: path_provider 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "2.0.7" 203 | path_provider_android: 204 | dependency: transitive 205 | description: 206 | name: path_provider_android 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "2.0.9" 210 | path_provider_ios: 211 | dependency: transitive 212 | description: 213 | name: path_provider_ios 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "2.0.7" 217 | path_provider_linux: 218 | dependency: transitive 219 | description: 220 | name: path_provider_linux 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "2.1.2" 224 | path_provider_macos: 225 | dependency: transitive 226 | description: 227 | name: path_provider_macos 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "2.0.4" 231 | path_provider_platform_interface: 232 | dependency: transitive 233 | description: 234 | name: path_provider_platform_interface 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "2.0.1" 238 | path_provider_windows: 239 | dependency: transitive 240 | description: 241 | name: path_provider_windows 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "2.0.4" 245 | pedantic: 246 | dependency: transitive 247 | description: 248 | name: pedantic 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "1.11.1" 252 | platform: 253 | dependency: transitive 254 | description: 255 | name: platform 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "3.1.0" 259 | plugin_platform_interface: 260 | dependency: transitive 261 | description: 262 | name: plugin_platform_interface 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "2.0.2" 266 | process: 267 | dependency: transitive 268 | description: 269 | name: process 270 | url: "https://pub.dartlang.org" 271 | source: hosted 272 | version: "4.2.4" 273 | rxdart: 274 | dependency: transitive 275 | description: 276 | name: rxdart 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "0.27.3" 280 | sky_engine: 281 | dependency: transitive 282 | description: flutter 283 | source: sdk 284 | version: "0.0.99" 285 | source_span: 286 | dependency: transitive 287 | description: 288 | name: source_span 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.8.1" 292 | sqflite: 293 | dependency: transitive 294 | description: 295 | name: sqflite 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "2.0.1" 299 | sqflite_common: 300 | dependency: transitive 301 | description: 302 | name: sqflite_common 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "2.0.1+1" 306 | string_scanner: 307 | dependency: transitive 308 | description: 309 | name: string_scanner 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "1.1.0" 313 | synchronized: 314 | dependency: transitive 315 | description: 316 | name: synchronized 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "3.0.0" 320 | term_glyph: 321 | dependency: transitive 322 | description: 323 | name: term_glyph 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "1.2.0" 327 | typed_data: 328 | dependency: transitive 329 | description: 330 | name: typed_data 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.3.0" 334 | uuid: 335 | dependency: transitive 336 | description: 337 | name: uuid 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "3.0.5" 341 | vector_math: 342 | dependency: transitive 343 | description: 344 | name: vector_math 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "2.1.0" 348 | win32: 349 | dependency: transitive 350 | description: 351 | name: win32 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "2.3.1" 355 | xdg_directories: 356 | dependency: transitive 357 | description: 358 | name: xdg_directories 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "0.2.0" 362 | sdks: 363 | dart: ">=2.14.0 <3.0.0" 364 | flutter: ">=2.5.0" 365 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:christian_lyrics/lyric.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'dart:async'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:christian_lyrics/christian_lyrics.dart'; 7 | 8 | import 'package:audio_session/audio_session.dart'; 9 | import 'package:just_audio/just_audio.dart'; 10 | import 'package:rxdart/rxdart.dart'; 11 | 12 | import 'common.dart'; 13 | 14 | const lyricText = "1\r\n00:00:01,000 --> 00:00:29,950\r\n* * *\r\n\r\n2\r\n00:00:30,000 --> 00:00:33,350\r\nOh holy night!\r\n\r\n3\r\n00:00:33,400 --> 00:00:37,950\r\nThe stars are brightly shining\r\n\r\n4\r\n00:00:38,000 --> 00:00:46,950\r\nIt is the night of our dear savior's birth\r\n\r\n5\r\n00:00:47,000 --> 00:00:54,950\r\nLong lay the world in sin and error, pining\r\n\r\n6\r\n00:00:55,000 --> 00:01:02,950\r\n'Til He appear'd and the soul felt it's worth.\r\n\r\n7\r\n00:01:03,000 --> 00:01:03,950\r\n \r\n\r\n8\r\n00:01:04,000 --> 00:01:11,350\r\nA thrill of hope, the weary world rejoices\r\n\r\n9\r\n00:01:11,400 --> 00:01:19,950\r\nFor yonder breaks a new and glorious morn\r\n\r\n10\r\n00:01:20,000 --> 00:01:33,950\r\nFall on your knees! O hear the angel voices!\r\n\r\n11\r\n00:01:34,000 --> 00:01:34,950\r\n \r\n\r\n12\r\n00:01:35,000 --> 00:01:48,950\r\nO night divine, O night when Christ was born;\r\n\r\n13\r\n00:01:49,000 --> 00:02:06,950\r\nO night divine, O night, O night Divine.\r\n\r\n14\r\n00:02:07,000 --> 00:02:21,950\r\n \r\n\r\n15\r\n00:02:22,000 --> 00:02:30,950\r\nTruly He taught us to love one another;\r\n\r\n16\r\n00:02:31,000 --> 00:02:39,950\r\nHis law is love and His gospel is peace.\r\n\r\n17\r\n00:02:40,000 --> 00:02:41,950\r\nChains shall He break for the slave is our brother;\r\n\r\n18\r\n00:02:42,000 --> 00:02:56,950\r\nAnd in His name all oppression shall cease.\r\n\r\n19\r\n00:02:57,000 --> 00:03:04,950\r\nSweet hymns of joy in grateful chorus raise we,\r\n\r\n20\r\n00:03:05,000 --> 00:03:12,950\r\nLet all within us praise His holy name.\r\n\r\n21\r\n00:03:13,000 --> 00:03:28,950\r\nChrist is the Lord! O praise His Name forever,\r\n\r\n22\r\n00:03:29,000 --> 00:03:42,950\r\nHis power and glory evermore proclaim.\r\n\r\n23\r\n00:03:43,000 --> 00:03:53,000\r\nHis power and glory evermore proclaim."; 15 | 16 | void main() { 17 | runApp(const MyApp()); 18 | } 19 | 20 | class MyApp extends StatefulWidget { 21 | const MyApp({Key? key}) : super(key: key); 22 | 23 | @override 24 | State createState() => _MyAppState(); 25 | } 26 | 27 | class _MyAppState extends State with WidgetsBindingObserver { 28 | 29 | final _player = AudioPlayer(); 30 | final christianLyrics = ChristianLyrics(); 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | WidgetsBinding.instance?.addObserver(this); 36 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 37 | statusBarColor: Colors.black, 38 | )); 39 | _init(); 40 | christianLyrics.setLyricContent(lyricText); 41 | } 42 | 43 | Future _init() async { 44 | // Inform the operating system of our app's audio attributes etc. 45 | // We pick a reasonable default for an app that plays speech. 46 | final session = await AudioSession.instance; 47 | await session.configure(AudioSessionConfiguration.speech()); 48 | // Listen to errors during playback. 49 | _player.playbackEventStream.listen((event) {}, 50 | onError: (Object e, StackTrace stackTrace) { 51 | print('A stream error occurred: $e'); 52 | }); 53 | // Try to load audio from a source and catch any errors. 54 | try { 55 | //print("${(await getApplicationDocumentsDirectory()).path}"); 56 | 57 | await _player.setAudioSource(AudioSource.uri(Uri.parse( 58 | "asset:///assets/OHolyNight_Christmas2016.mp3"))); 59 | } catch (e) { 60 | print("Error loading audio source: $e"); 61 | } 62 | } 63 | 64 | @override 65 | void dispose() { 66 | WidgetsBinding.instance?.removeObserver(this); 67 | // Release decoders and buffers back to the operating system making them 68 | // available for other apps to use. 69 | _player.dispose(); 70 | super.dispose(); 71 | } 72 | 73 | @override 74 | void didChangeAppLifecycleState(AppLifecycleState state) { 75 | if (state == AppLifecycleState.paused) { 76 | // Release the player's resources when not in use. We use "stop" so that 77 | // if the app resumes later, it will still remember what position to 78 | // resume from. 79 | _player.stop(); 80 | } 81 | } 82 | 83 | /// Collects the data useful for displaying in a seek bar, using a handy 84 | /// feature of rx_dart to combine the 3 streams of interest into one. 85 | Stream get _positionDataStream => 86 | Rx.combineLatest3( 87 | _player.positionStream, 88 | _player.bufferedPositionStream, 89 | _player.durationStream, 90 | (position, bufferedPosition, duration) => PositionData( 91 | position, bufferedPosition, duration ?? Duration.zero)); 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return MaterialApp( 96 | debugShowCheckedModeBanner: false, 97 | home: Scaffold( 98 | body: SafeArea( 99 | child: Column( 100 | crossAxisAlignment: CrossAxisAlignment.center, 101 | mainAxisAlignment: MainAxisAlignment.center, 102 | children: [ 103 | Expanded(child: Container(child: StreamBuilder( 104 | stream: _player.playerStateStream, 105 | builder: (context, snapshot) { 106 | final playerState = snapshot.data; 107 | final playing = playerState?.playing ?? false; 108 | return christianLyrics.getLyric(context, isPlaying: playing); 109 | }), color: Colors.brown)), 110 | 111 | // Display play/pause button and volume/speed sliders. 112 | ControlButtons(_player, christianLyrics), 113 | // Display seek bar. Using StreamBuilder, this widget rebuilds 114 | // each time the position, buffered position or duration changes. 115 | StreamBuilder( 116 | stream: _positionDataStream, 117 | builder: (context, snapshot) { 118 | final positionData = snapshot.data; 119 | 120 | if (positionData != null) { 121 | christianLyrics.setPositionWithOffset(position: positionData.position.inMilliseconds, duration: positionData.duration.inMilliseconds); 122 | } 123 | 124 | return SeekBar( 125 | duration: positionData?.duration ?? Duration.zero, 126 | position: positionData?.position ?? Duration.zero, 127 | bufferedPosition: 128 | positionData?.bufferedPosition ?? Duration.zero, 129 | onChangeEnd: (Duration d) { 130 | christianLyrics.resetLyric(); 131 | christianLyrics.setPositionWithOffset(position: d.inMilliseconds, duration: positionData!.duration.inMilliseconds); 132 | _player.seek(d); 133 | }, 134 | ); 135 | } 136 | ), 137 | ], 138 | ), 139 | ), 140 | ), 141 | ); 142 | } 143 | } 144 | 145 | /// Displays the play/pause button and volume/speed sliders. 146 | class ControlButtons extends StatelessWidget { 147 | final AudioPlayer player; 148 | final ChristianLyrics christianLyrics; 149 | 150 | ControlButtons(this.player, this.christianLyrics); 151 | 152 | @override 153 | Widget build(BuildContext context) { 154 | return Row( 155 | mainAxisSize: MainAxisSize.min, 156 | children: [ 157 | // Opens volume slider dialog 158 | IconButton( 159 | icon: Icon(Icons.volume_up), 160 | onPressed: () { 161 | showSliderDialog( 162 | context: context, 163 | title: "Adjust volume", 164 | divisions: 10, 165 | min: 0.0, 166 | max: 1.0, 167 | value: player.volume, 168 | stream: player.volumeStream, 169 | onChanged: player.setVolume, 170 | ); 171 | }, 172 | ), 173 | 174 | /// This StreamBuilder rebuilds whenever the player state changes, which 175 | /// includes the playing/paused state and also the 176 | /// loading/buffering/ready state. Depending on the state we show the 177 | /// appropriate button or loading indicator. 178 | StreamBuilder( 179 | stream: player.playerStateStream, 180 | builder: (context, snapshot) { 181 | final playerState = snapshot.data; 182 | final processingState = playerState?.processingState; 183 | final playing = playerState?.playing; 184 | if (processingState == ProcessingState.loading || 185 | processingState == ProcessingState.buffering) { 186 | return Container( 187 | margin: EdgeInsets.all(8.0), 188 | width: 64.0, 189 | height: 64.0, 190 | child: CircularProgressIndicator(), 191 | ); 192 | } else if (playing != true) { 193 | return IconButton( 194 | icon: Icon(Icons.play_arrow), 195 | iconSize: 64.0, 196 | onPressed: () { 197 | christianLyrics.resetLyric(); 198 | player.play(); 199 | }, 200 | ); 201 | } else if (processingState != ProcessingState.completed) { 202 | return IconButton( 203 | icon: Icon(Icons.pause), 204 | iconSize: 64.0, 205 | onPressed: player.pause, 206 | ); 207 | } else { 208 | return IconButton( 209 | icon: Icon(Icons.replay), 210 | iconSize: 64.0, 211 | onPressed: () => player.seek(Duration.zero), 212 | ); 213 | } 214 | }, 215 | ), 216 | // Opens speed slider dialog 217 | StreamBuilder( 218 | stream: player.speedStream, 219 | builder: (context, snapshot) => IconButton( 220 | icon: Text("${snapshot.data?.toStringAsFixed(1)}x", 221 | style: TextStyle(fontWeight: FontWeight.bold)), 222 | onPressed: () { 223 | showSliderDialog( 224 | context: context, 225 | title: "Adjust speed", 226 | divisions: 10, 227 | min: 0.5, 228 | max: 1.5, 229 | value: player.speed, 230 | stream: player.speedStream, 231 | onChanged: player.setSpeed, 232 | ); 233 | }, 234 | ), 235 | ), 236 | ], 237 | ); 238 | } 239 | } -------------------------------------------------------------------------------- /lib/lyric.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:ui' as ui; 4 | import 'package:async/async.dart'; 5 | import 'package:christian_lyrics/srt_parser.dart'; 6 | import 'package:flutter/gestures.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | const _enable_paint_debug = false; 10 | 11 | class Lyric extends StatefulWidget { 12 | 13 | Lyric({ 14 | required this.lyric, 15 | required this.size, 16 | required this.playing, 17 | this.lyricLineStyle, 18 | this.streamPosition, 19 | this.textAlign = TextAlign.center, 20 | this.highlight = Colors.red, 21 | this.onTap, 22 | }) : assert(lyric.size > 0); 23 | 24 | StreamController? streamPosition; 25 | 26 | final TextStyle? lyricLineStyle; 27 | 28 | final LyricContent lyric; 29 | 30 | final TextAlign textAlign; 31 | 32 | int position = 0; 33 | 34 | final Color highlight; 35 | 36 | final Size size; 37 | 38 | final VoidCallback? onTap; 39 | 40 | final bool playing; 41 | 42 | @override 43 | State createState() => LyricState(); 44 | } 45 | 46 | class LyricState extends State with TickerProviderStateMixin { 47 | LyricPainter? lyricPainter; 48 | 49 | AnimationController? _flingController; 50 | 51 | AnimationController? _lineController; 52 | 53 | AnimationController? _gradientController; 54 | 55 | @override 56 | void initState() { 57 | super.initState(); 58 | lyricPainter = LyricPainter( 59 | widget.lyricLineStyle!, 60 | widget.lyric, 61 | textAlign: widget.textAlign, 62 | highlight: widget.highlight, 63 | ); 64 | _scrollToCurrentPosition(widget.position); 65 | } 66 | 67 | @override 68 | void didUpdateWidget(Lyric oldWidget) { 69 | 70 | //super.didUpdateWidget(oldWidget); 71 | 72 | if (widget.lyric != oldWidget.lyric) { 73 | lyricPainter = LyricPainter( 74 | widget.lyricLineStyle!, 75 | widget.lyric, 76 | textAlign: widget.textAlign, 77 | highlight: widget.highlight, 78 | ); 79 | } 80 | 81 | // if (widget.position != oldWidget.position) { 82 | // _scrollToCurrentPosition(widget.position); 83 | // } 84 | 85 | if (widget.playing != oldWidget.playing) { 86 | if (!widget.playing) { 87 | _gradientController?.stop(); 88 | } else { 89 | _gradientController?.forward(); 90 | } 91 | } 92 | } 93 | 94 | /// scroll lyric to current playing position 95 | void _scrollToCurrentPosition(int milliseconds, {bool animate = true}) { 96 | 97 | if (lyricPainter!.height == -1) { 98 | WidgetsBinding.instance!.addPostFrameCallback((d) { 99 | // debugPrint("try to init scroll to position ${widget.position.value}," 100 | // "but lyricPainter is unavaiable, so scroll(without animate) on next frame $d"); 101 | //TODO maybe cause bad performance 102 | if (mounted) _scrollToCurrentPosition(milliseconds, animate: false); 103 | }); 104 | return; 105 | } 106 | 107 | int line = widget.lyric.findLineByTimeStamp(milliseconds, lyricPainter!.currentLine); 108 | 109 | if (lyricPainter!.currentLine != line && !dragging) { 110 | double offset = lyricPainter!.computeScrollTo(line); 111 | 112 | if (animate) { 113 | //print('inject'); 114 | _lineController?.dispose(); 115 | _lineController = AnimationController( 116 | vsync: this, 117 | duration: Duration(milliseconds: 1000), 118 | )..addStatusListener((status) { 119 | if (status == AnimationStatus.completed) { 120 | _lineController!.dispose(); 121 | _lineController = null; 122 | } 123 | }); 124 | 125 | Animation animation = 126 | Tween(begin: lyricPainter!.offsetScroll, end: lyricPainter!.offsetScroll + offset) 127 | .chain(CurveTween(curve: Curves.easeInOut)) 128 | .animate(_lineController!); 129 | animation.addListener(() { 130 | //print(animation.value); 131 | //lyricPainter!.offsetScroll = animation.value; 132 | }); 133 | _lineController!.forward(); 134 | 135 | _gradientController?.dispose(); 136 | final entry = widget.lyric[line]; 137 | final startPercent = (milliseconds - entry.position) / entry.duration; 138 | _gradientController = AnimationController( 139 | vsync: this, 140 | duration: Duration(milliseconds: (entry.duration - entry.position).toInt()), 141 | ); 142 | _gradientController!.addListener(() { 143 | lyricPainter!.lineGradientPercent = _gradientController!.value; 144 | }); 145 | if (widget.playing) { 146 | _gradientController!.forward(from: startPercent); 147 | } else { 148 | _gradientController!.value = startPercent; 149 | } 150 | 151 | } else { 152 | lyricPainter!.offsetScroll += offset; 153 | } 154 | 155 | } 156 | lyricPainter!.currentLine = line; 157 | } 158 | 159 | bool dragging = false; 160 | 161 | bool _consumeTap = false; 162 | 163 | @override 164 | void dispose() { 165 | _flingController?.dispose(); 166 | _flingController = null; 167 | _lineController?.dispose(); 168 | _lineController = null; 169 | _gradientController?.dispose(); 170 | _gradientController = null; 171 | super.dispose(); 172 | } 173 | 174 | Widget? cacheOld; 175 | 176 | @override 177 | Widget build(BuildContext _) { 178 | return Container( 179 | constraints: BoxConstraints(minWidth: 300, minHeight: 120), 180 | child: GestureDetector( 181 | onTap: () { 182 | if (!_consumeTap && widget.onTap != null) { 183 | widget.onTap!(); 184 | } else { 185 | _consumeTap = false; 186 | } 187 | }, 188 | onTapDown: (details) { 189 | if (dragging) { 190 | _consumeTap = true; 191 | 192 | dragging = false; 193 | _flingController?.dispose(); 194 | _flingController = null; 195 | } 196 | }, 197 | onVerticalDragStart: (details) { 198 | dragging = true; 199 | _flingController?.dispose(); 200 | _flingController = null; 201 | }, 202 | onVerticalDragUpdate: (details) { 203 | //debugPrint("details.primaryDelta : ${details.primaryDelta}"); 204 | lyricPainter!.offsetScroll += details.primaryDelta!; 205 | }, 206 | onVerticalDragEnd: (details) { 207 | _flingController = AnimationController.unbounded( 208 | vsync: this, 209 | duration: const Duration(milliseconds: 300), 210 | ) 211 | ..addListener(() { 212 | double value = _flingController!.value; 213 | 214 | if (value < -lyricPainter!.height || value >= 0) { 215 | _flingController!.dispose(); 216 | _flingController = null; 217 | dragging = false; 218 | value = value.clamp(-lyricPainter!.height, 0.0); 219 | } 220 | lyricPainter!.offsetScroll = value; 221 | lyricPainter!.repaint(); 222 | }) 223 | ..addStatusListener((status) { 224 | if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) { 225 | dragging = false; 226 | _flingController?.dispose(); 227 | _flingController = null; 228 | } 229 | }) 230 | ..animateWith( 231 | ClampingScrollSimulation(position: lyricPainter!.offsetScroll, velocity: details.primaryVelocity!)); 232 | }, 233 | child: Stack( 234 | children: [ 235 | RepaintBoundary(child: CustomPaint( 236 | isComplex: true, 237 | willChange: true, 238 | size: widget.size, 239 | painter: lyricPainter, 240 | )), 241 | 242 | StreamBuilder( 243 | stream: widget.streamPosition!.stream, 244 | builder: (BuildContext context, AsyncSnapshot snapshot) { 245 | final result = snapshot.data ?? 0; 246 | widget.position = result; 247 | _scrollToCurrentPosition(widget.position); 248 | return const Center(); 249 | } 250 | ) 251 | 252 | ], 253 | ), 254 | ), 255 | ); 256 | } 257 | } 258 | 259 | class LyricPainter extends ChangeNotifier implements CustomPainter { 260 | 261 | bool preload = false; 262 | int maxLinePaint = 3; 263 | double tmpPreDy = 0; 264 | 265 | LyricContent? lyric; 266 | //List lyric; 267 | late List lyricPainters; 268 | 269 | TextPainter _highlightPainter = TextPainter(textDirection: TextDirection.ltr); 270 | 271 | double _offsetScroll = 0; 272 | 273 | double get offsetScroll => _offsetScroll; 274 | 275 | double _lineGradientPercent = -1; 276 | 277 | double get lineGradientPercent { 278 | if (_lineGradientPercent == -1) return 1.0; 279 | return _lineGradientPercent.clamp(0.0, 1.0); 280 | } 281 | 282 | set lineGradientPercent(double percent) { 283 | _lineGradientPercent = percent; 284 | repaint(); 285 | } 286 | 287 | set offsetScroll(double value) { 288 | if (height == -1) return; // do not change offset when height is not available. 289 | _offsetScroll = value.clamp(-height, 0.0); 290 | repaint(); 291 | } 292 | 293 | int currentLine = 0; 294 | 295 | TextAlign textAlign; 296 | 297 | late TextStyle _styleHighlight; 298 | 299 | ///param lyric must not be null 300 | LyricPainter(TextStyle style, this.lyric, {this.textAlign = TextAlign.center, Color highlight = Colors.red}) { 301 | assert(lyric != null); 302 | lyricPainters = []; 303 | for (int i = 0; i < lyric!.size; i++) { 304 | var painter = TextPainter(text: TextSpan(style: style, text: lyric![i].line), textAlign: textAlign); 305 | painter.textDirection = TextDirection.ltr; 306 | // painter.layout();//layout first, to get the height 307 | lyricPainters.add(painter); 308 | } 309 | _styleHighlight = style.copyWith(color: highlight); 310 | } 311 | 312 | void repaint() { 313 | notifyListeners(); 314 | } 315 | 316 | double get height => _height; 317 | double _height = -1; 318 | 319 | @override 320 | void paint(ui.Canvas canvas, ui.Size size) { 321 | 322 | //print('re paint'); 323 | 324 | _layoutPainterList(size); 325 | canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); 326 | 327 | double dy = offsetScroll + size.height / 2 - lyricPainters[0].height / 2; 328 | 329 | for (int line = 0; line < lyricPainters.length; line++) { 330 | TextPainter painter = lyricPainters[line]; 331 | 332 | if (line == currentLine) { 333 | _paintCurrentLine(canvas, painter, dy, size); 334 | } else { 335 | //if (currentLine + maxLinePaint >= line && currentLine - maxLinePaint <= line) { 336 | drawLine(canvas, painter, dy, size); 337 | //} 338 | } 339 | dy += painter.height; 340 | } 341 | 342 | tmpPreDy = dy; 343 | } 344 | 345 | void _paintCurrentLine(ui.Canvas canvas, TextPainter painter, double dy, ui.Size size) { 346 | if (dy > size.height || dy < 0 - painter.height) { 347 | return; 348 | } 349 | 350 | //for current highlight line, draw background text first 351 | drawLine(canvas, painter, dy, size); 352 | 353 | _highlightPainter 354 | ..text = TextSpan(text: (painter.text as TextSpan).text, style: _styleHighlight) 355 | ..textAlign = textAlign; 356 | 357 | _highlightPainter.layout(); //layout with unbound width 358 | 359 | double lineWidth = _highlightPainter.width; 360 | double gradientWidth = _highlightPainter.width * lineGradientPercent; 361 | final double lineHeight = _highlightPainter.height; 362 | 363 | _highlightPainter.layout(maxWidth: size.width); 364 | 365 | final highlightRegion = Path(); 366 | double lineDy = 0; 367 | while (gradientWidth > 0) { 368 | double dx = 0; 369 | if (lineWidth < size.width) { 370 | dx = (size.width - lineWidth) / 2; 371 | } 372 | highlightRegion.addRect(Rect.fromLTWH(0, dy + lineDy, dx + gradientWidth, lineHeight)); 373 | lineWidth -= _highlightPainter.width; 374 | gradientWidth -= _highlightPainter.width; 375 | lineDy += lineHeight; 376 | } 377 | 378 | canvas.save(); 379 | canvas.clipPath(highlightRegion); 380 | 381 | drawLine(canvas, _highlightPainter, dy, size); 382 | canvas.restore(); 383 | 384 | assert(() { 385 | if (_enable_paint_debug) { 386 | final painter = Paint() 387 | ..color = Colors.black 388 | ..style = PaintingStyle.stroke 389 | ..strokeWidth = 1; 390 | canvas.drawPath(highlightRegion, painter); 391 | } 392 | return true; 393 | }()); 394 | } 395 | 396 | ///draw a lyric line 397 | void drawLine(ui.Canvas canvas, TextPainter painter, double dy, ui.Size size) { 398 | if (dy > size.height || dy < 0 - painter.height) { 399 | return; 400 | } 401 | canvas.save(); 402 | canvas.translate(_calculateAlignOffset(painter, size), dy); 403 | 404 | painter.paint(canvas, Offset.zero); 405 | canvas.restore(); 406 | } 407 | 408 | double _calculateAlignOffset(TextPainter painter, ui.Size size) { 409 | if (textAlign == TextAlign.center) { 410 | return (size.width - painter.width) / 2; 411 | } 412 | return 0; 413 | } 414 | 415 | @override 416 | bool shouldRepaint(LyricPainter oldDelegate) { 417 | return true; 418 | } 419 | 420 | void _layoutPainterList(ui.Size size) { 421 | _height = 0; 422 | lyricPainters.forEach((p) { 423 | p.layout(maxWidth: size.width); 424 | _height += p.height; 425 | }); 426 | } 427 | 428 | //compute the offset current offset to destination line 429 | double computeScrollTo(int destination) { 430 | if (lyricPainters.length <= 0 || this.height == 0) { 431 | return 0; 432 | } 433 | 434 | double height = -lyricPainters[0].height / 2; 435 | for (int i = 0; i < lyricPainters.length; i++) { 436 | if (i == destination) { 437 | height += lyricPainters[i].height / 2; 438 | break; 439 | } 440 | height += lyricPainters[i].height; 441 | } 442 | return -(height + offsetScroll); 443 | } 444 | 445 | @override 446 | bool? hitTest(ui.Offset position) => null; 447 | 448 | @override 449 | get semanticsBuilder => null; 450 | 451 | @override 452 | bool shouldRebuildSemantics(LyricPainter oldDelegate) => shouldRepaint((oldDelegate)); 453 | } 454 | 455 | class LyricContent { 456 | ///splitter lyric content to line 457 | static const LineSplitter _SPLITTER = const LineSplitter(); 458 | 459 | static const int _default_line_duration = 5 * 1000; 460 | 461 | LyricContent.from(String text) { 462 | 463 | //Parse srt 464 | List srtContent = parseSrt(text); 465 | 466 | for (Subtitle item in srtContent) { 467 | _durations.add(item.range!.begin); 468 | _lyricEntries.add(LyricEntry(item.rawLines.join("\n"), item.range!.begin, item.range!.end)); 469 | } 470 | } 471 | 472 | List _durations = []; 473 | List _lyricEntries = []; 474 | 475 | int get size => _durations.length; 476 | 477 | LyricEntry operator [](int index) { 478 | return _lyricEntries[index]; 479 | } 480 | 481 | int _getTimeStamp(int index) { 482 | return _durations[index]; 483 | } 484 | 485 | LyricEntry? getLineByTimeStamp(final int timeStamp, final int anchorLine) { 486 | if (size <= 0) { 487 | return null; 488 | } 489 | final line = findLineByTimeStamp(timeStamp, anchorLine); 490 | return this[line]; 491 | } 492 | 493 | int findLineByTimeStamp(final int timeStamp, final int anchorLine) { 494 | int position = anchorLine; 495 | if (position < 0 || position > size - 1) { 496 | position = 0; 497 | } 498 | if (_getTimeStamp(position) > timeStamp) { 499 | //look forward 500 | while (_getTimeStamp(position) > timeStamp) { 501 | position--; 502 | if (position <= 0) { 503 | position = 0; 504 | break; 505 | } 506 | } 507 | } else { 508 | while (_getTimeStamp(position) < timeStamp) { 509 | position++; 510 | if (position <= size - 1 && _getTimeStamp(position) > timeStamp) { 511 | position--; 512 | break; 513 | } 514 | if (position >= size - 1) { 515 | position = size - 1; 516 | break; 517 | } 518 | } 519 | } 520 | return position; 521 | } 522 | 523 | @override 524 | String toString() { 525 | return 'Lyric{_lyricEntries: $_lyricEntries}'; 526 | } 527 | } 528 | 529 | class LyricEntry { 530 | static RegExp pattern = RegExp(r"\[\d{2}:\d{2}.\d{2,3}]"); 531 | 532 | static int _stamp2int(final String stamp) { 533 | final int indexOfColon = stamp.indexOf(":"); 534 | final int indexOfPoint = stamp.indexOf("."); 535 | 536 | final int minute = int.parse(stamp.substring(1, indexOfColon)); 537 | final int second = int.parse(stamp.substring(indexOfColon + 1, indexOfPoint)); 538 | int millisecond; 539 | if (stamp.length - indexOfPoint == 2) { 540 | millisecond = int.parse(stamp.substring(indexOfPoint + 1, stamp.length)) * 10; 541 | } else { 542 | millisecond = int.parse(stamp.substring(indexOfPoint + 1, stamp.length - 1)); 543 | } 544 | return ((((minute * 60) + second) * 1000) + millisecond); 545 | } 546 | 547 | ///build from a .lrc file line .such as: [11:44.100] what makes your beautiful 548 | static void inflate(String line, Map map) { 549 | //TODO lyric info 550 | if (line.startsWith("[ti:")) { 551 | } else if (line.startsWith("[ar:")) { 552 | } else if (line.startsWith("[al:")) { 553 | } else if (line.startsWith("[au:")) { 554 | } else if (line.startsWith("[by:")) { 555 | } else { 556 | var stamps = pattern.allMatches(line); 557 | var content = line.split(pattern).last; 558 | stamps.forEach((stamp) { 559 | int timeStamp = _stamp2int(stamp.group(0)!); 560 | map[timeStamp] = content; 561 | }); 562 | } 563 | } 564 | 565 | LyricEntry(this.line, this.position, this.duration) : this.timeStamp = getTimeStamp(position); 566 | 567 | final String timeStamp; 568 | final String line; 569 | 570 | final int position; 571 | 572 | ///the duration of this line 573 | final int duration; 574 | 575 | @override 576 | String toString() { 577 | return 'LyricEntry{line: $line, timeStamp: $timeStamp}'; 578 | } 579 | 580 | @override 581 | bool operator ==(Object other) => 582 | identical(this, other) || 583 | other is LyricEntry && runtimeType == other.runtimeType && line == other.line && timeStamp == other.timeStamp; 584 | 585 | @override 586 | int get hashCode => line.hashCode ^ timeStamp.hashCode; 587 | } 588 | 589 | String getTimeStamp(int milliseconds) { 590 | int seconds = (milliseconds / 1000).truncate(); 591 | int minutes = (seconds / 60).truncate(); 592 | 593 | String minutesStr = (minutes % 60).toString().padLeft(2, '0'); 594 | String secondsStr = (seconds % 60).toString().padLeft(2, '0'); 595 | 596 | return "$minutesStr:$secondsStr"; 597 | } 598 | 599 | class PlayingLyric { 600 | 601 | CancelableOperation? _lyricLoader; 602 | 603 | String _message = 'Lyric not found'; 604 | 605 | LyricContent? _lyricContent; 606 | List? _lyricContentSrt; 607 | 608 | ///[lyric],[lyric]=null,[message]=null 609 | String get message => _message; 610 | 611 | LyricContent? get lyric => _lyricContent; 612 | 613 | bool get hasLyric => lyric != null && lyric!.size > 0; 614 | 615 | //Music _music; 616 | void setLyric({String? lyric}) { 617 | if (lyric != null && lyric.isNotEmpty) { 618 | _lyricContent = LyricContent.from(lyric); 619 | } else { 620 | _lyricContent = null; 621 | } 622 | if (_lyricContent?.size == 0) { 623 | _lyricContent = null; 624 | } 625 | if (_lyricContent == null) { 626 | _message = 'Lyric content empty'; 627 | } 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | A8A73A843F1C45859C0DF740 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D627776DA5BC12D871887EBE /* Pods_Runner.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 1DF7E2A5858EE82D9FBBC9A4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 5154B738E8310C60850B740B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 38 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 39 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 77E917E86123D8EA38F50350 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | D627776DA5BC12D871887EBE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | A8A73A843F1C45859C0DF740 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 5B974CCA1D67993A77E03EBF /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | D627776DA5BC12D871887EBE /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 5CE56AE0CA0F52098030F7B5 /* Pods */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 1DF7E2A5858EE82D9FBBC9A4 /* Pods-Runner.debug.xcconfig */, 76 | 77E917E86123D8EA38F50350 /* Pods-Runner.release.xcconfig */, 77 | 5154B738E8310C60850B740B /* Pods-Runner.profile.xcconfig */, 78 | ); 79 | name = Pods; 80 | path = Pods; 81 | sourceTree = ""; 82 | }; 83 | 9740EEB11CF90186004384FC /* Flutter */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 87 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 88 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 89 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 90 | ); 91 | name = Flutter; 92 | sourceTree = ""; 93 | }; 94 | 97C146E51CF9000F007C117D = { 95 | isa = PBXGroup; 96 | children = ( 97 | 9740EEB11CF90186004384FC /* Flutter */, 98 | 97C146F01CF9000F007C117D /* Runner */, 99 | 97C146EF1CF9000F007C117D /* Products */, 100 | 5CE56AE0CA0F52098030F7B5 /* Pods */, 101 | 5B974CCA1D67993A77E03EBF /* Frameworks */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | 97C146EF1CF9000F007C117D /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 97C146EE1CF9000F007C117D /* Runner.app */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | 97C146F01CF9000F007C117D /* Runner */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 117 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 118 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 119 | 97C147021CF9000F007C117D /* Info.plist */, 120 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 121 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 122 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 123 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 124 | ); 125 | path = Runner; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 97C146ED1CF9000F007C117D /* Runner */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 134 | buildPhases = ( 135 | E627D32A4588BDC0F37A0F76 /* [CP] Check Pods Manifest.lock */, 136 | 9740EEB61CF901F6004384FC /* Run Script */, 137 | 97C146EA1CF9000F007C117D /* Sources */, 138 | 97C146EB1CF9000F007C117D /* Frameworks */, 139 | 97C146EC1CF9000F007C117D /* Resources */, 140 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 142 | 496AAC24C4A0CE56A96A516E /* [CP] Embed Pods Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = Runner; 149 | productName = Runner; 150 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | 97C146E61CF9000F007C117D /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastUpgradeCheck = 1020; 160 | ORGANIZATIONNAME = ""; 161 | TargetAttributes = { 162 | 97C146ED1CF9000F007C117D = { 163 | CreatedOnToolsVersion = 7.3.1; 164 | LastSwiftMigration = 1100; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 97C146E51CF9000F007C117D; 177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 97C146ED1CF9000F007C117D /* Runner */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 97C146EC1CF9000F007C117D /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | name = "Thin Binary"; 209 | outputPaths = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | shellPath = /bin/sh; 213 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 214 | }; 215 | 496AAC24C4A0CE56A96A516E /* [CP] Embed Pods Frameworks */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputFileListPaths = ( 221 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 222 | ); 223 | name = "[CP] Embed Pods Frameworks"; 224 | outputFileListPaths = ( 225 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | shellPath = /bin/sh; 229 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 230 | showEnvVarsInLog = 0; 231 | }; 232 | 9740EEB61CF901F6004384FC /* Run Script */ = { 233 | isa = PBXShellScriptBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | inputPaths = ( 238 | ); 239 | name = "Run Script"; 240 | outputPaths = ( 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | shellPath = /bin/sh; 244 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 245 | }; 246 | E627D32A4588BDC0F37A0F76 /* [CP] Check Pods Manifest.lock */ = { 247 | isa = PBXShellScriptBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | ); 251 | inputFileListPaths = ( 252 | ); 253 | inputPaths = ( 254 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 255 | "${PODS_ROOT}/Manifest.lock", 256 | ); 257 | name = "[CP] Check Pods Manifest.lock"; 258 | outputFileListPaths = ( 259 | ); 260 | outputPaths = ( 261 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | shellPath = /bin/sh; 265 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 266 | showEnvVarsInLog = 0; 267 | }; 268 | /* End PBXShellScriptBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | 97C146EA1CF9000F007C117D /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 276 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXSourcesBuildPhase section */ 281 | 282 | /* Begin PBXVariantGroup section */ 283 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | 97C146FB1CF9000F007C117D /* Base */, 287 | ); 288 | name = Main.storyboard; 289 | sourceTree = ""; 290 | }; 291 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 292 | isa = PBXVariantGroup; 293 | children = ( 294 | 97C147001CF9000F007C117D /* Base */, 295 | ); 296 | name = LaunchScreen.storyboard; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXVariantGroup section */ 300 | 301 | /* Begin XCBuildConfiguration section */ 302 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | SUPPORTED_PLATFORMS = iphoneos; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Profile; 351 | }; 352 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 353 | isa = XCBuildConfiguration; 354 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | CLANG_ENABLE_MODULES = YES; 358 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 359 | ENABLE_BITCODE = NO; 360 | INFOPLIST_FILE = Runner/Info.plist; 361 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 362 | PRODUCT_BUNDLE_IDENTIFIER = com.christian.unity.christianLyricsExample; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 365 | SWIFT_VERSION = 5.0; 366 | VERSIONING_SYSTEM = "apple-generic"; 367 | }; 368 | name = Profile; 369 | }; 370 | 97C147031CF9000F007C117D /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_NONNULL = YES; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 380 | CLANG_WARN_BOOL_CONVERSION = YES; 381 | CLANG_WARN_COMMA = YES; 382 | CLANG_WARN_CONSTANT_CONVERSION = YES; 383 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 384 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 385 | CLANG_WARN_EMPTY_BODY = YES; 386 | CLANG_WARN_ENUM_CONVERSION = YES; 387 | CLANG_WARN_INFINITE_RECURSION = YES; 388 | CLANG_WARN_INT_CONVERSION = YES; 389 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 391 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 394 | CLANG_WARN_STRICT_PROTOTYPES = YES; 395 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 396 | CLANG_WARN_UNREACHABLE_CODE = YES; 397 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 398 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 399 | COPY_PHASE_STRIP = NO; 400 | DEBUG_INFORMATION_FORMAT = dwarf; 401 | ENABLE_STRICT_OBJC_MSGSEND = YES; 402 | ENABLE_TESTABILITY = YES; 403 | GCC_C_LANGUAGE_STANDARD = gnu99; 404 | GCC_DYNAMIC_NO_PIC = NO; 405 | GCC_NO_COMMON_BLOCKS = YES; 406 | GCC_OPTIMIZATION_LEVEL = 0; 407 | GCC_PREPROCESSOR_DEFINITIONS = ( 408 | "DEBUG=1", 409 | "$(inherited)", 410 | ); 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 418 | MTL_ENABLE_DEBUG_INFO = YES; 419 | ONLY_ACTIVE_ARCH = YES; 420 | SDKROOT = iphoneos; 421 | TARGETED_DEVICE_FAMILY = "1,2"; 422 | }; 423 | name = Debug; 424 | }; 425 | 97C147041CF9000F007C117D /* Release */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | CLANG_ANALYZER_NONNULL = YES; 430 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 431 | CLANG_CXX_LIBRARY = "libc++"; 432 | CLANG_ENABLE_MODULES = YES; 433 | CLANG_ENABLE_OBJC_ARC = YES; 434 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 435 | CLANG_WARN_BOOL_CONVERSION = YES; 436 | CLANG_WARN_COMMA = YES; 437 | CLANG_WARN_CONSTANT_CONVERSION = YES; 438 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 439 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 440 | CLANG_WARN_EMPTY_BODY = YES; 441 | CLANG_WARN_ENUM_CONVERSION = YES; 442 | CLANG_WARN_INFINITE_RECURSION = YES; 443 | CLANG_WARN_INT_CONVERSION = YES; 444 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 445 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 448 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 449 | CLANG_WARN_STRICT_PROTOTYPES = YES; 450 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 451 | CLANG_WARN_UNREACHABLE_CODE = YES; 452 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 453 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 454 | COPY_PHASE_STRIP = NO; 455 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 456 | ENABLE_NS_ASSERTIONS = NO; 457 | ENABLE_STRICT_OBJC_MSGSEND = YES; 458 | GCC_C_LANGUAGE_STANDARD = gnu99; 459 | GCC_NO_COMMON_BLOCKS = YES; 460 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 461 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 462 | GCC_WARN_UNDECLARED_SELECTOR = YES; 463 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 464 | GCC_WARN_UNUSED_FUNCTION = YES; 465 | GCC_WARN_UNUSED_VARIABLE = YES; 466 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 467 | MTL_ENABLE_DEBUG_INFO = NO; 468 | SDKROOT = iphoneos; 469 | SUPPORTED_PLATFORMS = iphoneos; 470 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | VALIDATE_PRODUCT = YES; 473 | }; 474 | name = Release; 475 | }; 476 | 97C147061CF9000F007C117D /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 479 | buildSettings = { 480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 481 | CLANG_ENABLE_MODULES = YES; 482 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 483 | ENABLE_BITCODE = NO; 484 | INFOPLIST_FILE = Runner/Info.plist; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.christian.unity.christianLyricsExample; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 489 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 490 | SWIFT_VERSION = 5.0; 491 | VERSIONING_SYSTEM = "apple-generic"; 492 | }; 493 | name = Debug; 494 | }; 495 | 97C147071CF9000F007C117D /* Release */ = { 496 | isa = XCBuildConfiguration; 497 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 498 | buildSettings = { 499 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 500 | CLANG_ENABLE_MODULES = YES; 501 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 502 | ENABLE_BITCODE = NO; 503 | INFOPLIST_FILE = Runner/Info.plist; 504 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 505 | PRODUCT_BUNDLE_IDENTIFIER = com.christian.unity.christianLyricsExample; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 508 | SWIFT_VERSION = 5.0; 509 | VERSIONING_SYSTEM = "apple-generic"; 510 | }; 511 | name = Release; 512 | }; 513 | /* End XCBuildConfiguration section */ 514 | 515 | /* Begin XCConfigurationList section */ 516 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | 97C147031CF9000F007C117D /* Debug */, 520 | 97C147041CF9000F007C117D /* Release */, 521 | 249021D3217E4FDB00AE95B9 /* Profile */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | 97C147061CF9000F007C117D /* Debug */, 530 | 97C147071CF9000F007C117D /* Release */, 531 | 249021D4217E4FDB00AE95B9 /* Profile */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | /* End XCConfigurationList section */ 537 | }; 538 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 539 | } 540 | --------------------------------------------------------------------------------