master 8e6f3cf9ebad cached
95 files
241.5 KB
63.6k tokens
294 symbols
1 requests
Download .txt
Showing preview only (276K chars total). Download the full file or copy to clipboard to get everything.
Repository: yxwandroid/flutter_plugin_record
Branch: master
Commit: 8e6f3cf9ebad
Files: 95
Total size: 241.5 KB

Directory structure:
gitextract_l9gpf37n/

├── .gitattributes
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── android/
│   ├── .gitignore
│   ├── build.gradle
│   ├── gradle/
│   │   └── wrapper/
│   │       └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── settings.gradle
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           └── kotlin/
│               └── record/
│                   └── wilson/
│                       └── flutter/
│                           └── com/
│                               └── flutter_plugin_record/
│                                   ├── FlutterPluginRecordPlugin.kt
│                                   ├── timer/
│                                   │   ├── ITimer.java
│                                   │   ├── ITimerChangeCallback.java
│                                   │   ├── MTimer.java
│                                   │   └── TimerUtils.java
│                                   └── utils/
│                                       ├── AudioHandler.java
│                                       ├── DateUtils.java
│                                       ├── DialogUtil.java
│                                       ├── FileTool.java
│                                       ├── LogUtils.java
│                                       ├── PlayState.java
│                                       ├── PlayUtilsPlus.java
│                                       └── RecorderUtil.java
├── example/
│   ├── .flutter-plugins-dependencies
│   ├── .gitignore
│   ├── .metadata
│   ├── README.md
│   ├── android/
│   │   ├── .gitignore
│   │   ├── app/
│   │   │   ├── build.gradle
│   │   │   └── src/
│   │   │       ├── debug/
│   │   │       │   └── AndroidManifest.xml
│   │   │       ├── main/
│   │   │       │   ├── AndroidManifest.xml
│   │   │       │   ├── kotlin/
│   │   │       │   │   └── record/
│   │   │       │   │       └── wilson/
│   │   │       │   │           └── flutter/
│   │   │       │   │               └── com/
│   │   │       │   │                   └── flutter_plugin_record_example/
│   │   │       │   │                       └── MainActivity.kt
│   │   │       │   └── res/
│   │   │       │       ├── drawable/
│   │   │       │       │   └── launch_background.xml
│   │   │       │       ├── values/
│   │   │       │       │   └── styles.xml
│   │   │       │       └── xml/
│   │   │       │           └── network_security_config.xml
│   │   │       └── profile/
│   │   │           └── AndroidManifest.xml
│   │   ├── build.gradle
│   │   ├── gradle/
│   │   │   └── wrapper/
│   │   │       └── gradle-wrapper.properties
│   │   ├── gradle.properties
│   │   ├── res/
│   │   │   └── values/
│   │   │       └── strings_en.arb
│   │   └── settings.gradle
│   ├── ios/
│   │   ├── Flutter/
│   │   │   ├── .last_build_id
│   │   │   ├── AppFrameworkInfo.plist
│   │   │   ├── Debug.xcconfig
│   │   │   ├── Flutter.podspec
│   │   │   └── Release.xcconfig
│   │   ├── Podfile
│   │   ├── Runner/
│   │   │   ├── AppDelegate.swift
│   │   │   ├── Assets.xcassets/
│   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   └── Contents.json
│   │   │   │   └── LaunchImage.imageset/
│   │   │   │       ├── Contents.json
│   │   │   │       └── README.md
│   │   │   ├── Base.lproj/
│   │   │   │   ├── LaunchScreen.storyboard
│   │   │   │   └── Main.storyboard
│   │   │   ├── Info.plist
│   │   │   └── Runner-Bridging-Header.h
│   │   ├── Runner.xcodeproj/
│   │   │   ├── project.pbxproj
│   │   │   ├── project.xcworkspace/
│   │   │   │   ├── contents.xcworkspacedata
│   │   │   │   └── xcshareddata/
│   │   │   │       ├── IDEWorkspaceChecks.plist
│   │   │   │       └── WorkspaceSettings.xcsettings
│   │   │   └── xcshareddata/
│   │   │       └── xcschemes/
│   │   │           └── Runner.xcscheme
│   │   └── Runner.xcworkspace/
│   │       ├── contents.xcworkspacedata
│   │       └── xcshareddata/
│   │           ├── IDEWorkspaceChecks.plist
│   │           └── WorkspaceSettings.xcsettings
│   ├── lib/
│   │   ├── generated/
│   │   │   └── i18n.dart
│   │   ├── main.dart
│   │   ├── path_provider_screen.dart
│   │   ├── record_mp3_screen.dart
│   │   ├── record_screen.dart
│   │   └── wechat_record_screen.dart
│   ├── pubspec.yaml
│   └── test/
│       └── widget_test.dart
├── flutter_plugin_record.iml
├── ios/
│   ├── .gitignore
│   ├── Assets/
│   │   └── .gitkeep
│   ├── Classes/
│   │   ├── DPAudioPlayer.h
│   │   ├── DPAudioPlayer.m
│   │   ├── DPAudioRecorder.h
│   │   ├── DPAudioRecorder.m
│   │   ├── FlutterPluginRecordPlugin.h
│   │   ├── FlutterPluginRecordPlugin.m
│   │   ├── JX_GCDTimerManager.h
│   │   └── JX_GCDTimerManager.m
│   └── flutter_plugin_record.podspec
├── lib/
│   ├── const/
│   │   ├── play_state.dart
│   │   ├── record_state.dart
│   │   └── response.dart
│   ├── flutter_plugin_record.dart
│   ├── index.dart
│   ├── utils/
│   │   └── common_toast.dart
│   └── widgets/
│       ├── custom_overlay.dart
│       └── voice_widget.dart
├── pubspec.yaml
└── test/
    └── flutter_plugin_record_test.dart

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
*.yml linguist-language=Dart
*.java linguist-language=Dart
*.m linguist-language=Dart
*.h linguist-language=Dart

================================================
FILE: .gitignore
================================================
.DS_Store
.dart_tool/

.packages
.pub/

build/
.idea/

================================================
FILE: .metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
  revision: 68587a0916366e9512a78df22c44163d041dd5f3
  channel: stable

project_type: plugin


================================================
FILE: CHANGELOG.md
================================================
## 1.0.1

* 录音时长类型修改为 Double(和 iOS 端保持一致)

## 1.0.0

*  添加空安全相关逻辑
*  适配 flutter 2.0

## 0.3.5

*  修复ios 音频监听灵敏性问题

## 0.3.4

*  修复根据路径录制bug

## 0.3.3

*  修复根据路径录制bug

## 0.3.2

*  修复android 获取权限bug

## 0.3.1

*  更新IOS podfile

## 0.3.0
*   添加支持录制mp3

## 0.2.5
*   修复权限申请bug
## 0.2.4
*   修复播放音频文件的bug。
## 0.2.3
*   修复bug
## 0.2.2
*   修复bug
## 0.2.1
*   补充说明文档
## 0.2.0
*   实现暂停播放和继续播放功能
## 0.1.9

*   注释掉初始化插件就申请权限的问题

## 0.1.8

*   添加支持 Android 和IOS 播放在线wav音频
   
## 0.1.7

*   修复bug在未使用录音功能前,通过playByPath播发音频,音频可以正常播放,但无法监听到播放结束
   
## 0.1.6

*   添加android 在开始录制时进行权限验证判断
## 0.1.5

*   实现根据传递的路径进行语音录制

## 0.1.4

*  解决 android 申请权限失败问题
*  解决 快速点击发送语音录制停止不了问题

## 0.1.3

*  实现播放完成的回调监听


## 0.1.2

*  实现播放指定路径录音文件
## 0.1.1

*  格式代码
## 0.1.0

*  修复提示bug

## 0.0.9

*  添加记录录制时间的功能

## 0.0.8

*  重构项目为oc项目 解决播放oc 工程无法使用问题
## 0.0.7

*  更新readme

## 0.0.6

*  适配android 9.0 
*  解决ios集成到oc项目不成功问题 

## 0.0.5

*  添加 readme 说明 

## 0.0.4

*  添加example readme 

## 0.0.3

*  添加引入方式 

## 0.0.2

* 添加license

## 0.0.1

* 初始化项目 




================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
[TOC]


# 使用Flutter实现 仿微信录音的插件 
插件支持android 和IOS


-------
插件提供的功能
录制
1. 录制语音,
2. 播放录音,
3. 录制声音大小的监听
4. 提供录制时长的监听
5. 提供类似微信的录制组件
6. 提供播放音频结束的监听
7. 提供根据传递的路径进行语音录制
8. 提供录制wav,mp3格式  具体可参考example

播放
1. 提供播放指定路径的音频文件
2. 提供播放指定Url地址的wav,MP3格式文件
3. 提供播放完成的回调监听
4. 提供暂停和继续播放的功能
5. 提供停止播放的功能




## 1,引入
在pubspec.yaml 文件上引入如下配置


    引入方式1(引入最新的版本)
    flutter_plugin_record:
        git:
          url: https://github.com/yxwandroid/flutter_plugin_record.git
    
    引入方式2 (引入指定某次commit)
    flutter_plugin_record:
        git:
          url: https://github.com/yxwandroid/flutter_plugin_record.git
          ref: 29c02b15835907879451ad9f8f88c357149c6085
          
    引入方式3 (引入Flutter仓库的library)
          
    dependencies:
      flutter_plugin_record: ^1.0.1
              
              
          
          
          
          
        


### 使用
### 1, 初始化录制
#### 1.1, 初始化录制(wav)
可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化

    //实例化对象 
    FlutterPluginRecord   recordPlugin = new FlutterPluginRecord();
    //    初始化
    recordPlugin.init()
   

#### 1.2, 初始化录制(Mp3)
可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化

    //实例化对象 
    FlutterPluginRecord   recordPlugin = new FlutterPluginRecord();
    //    初始化
    recordPlugin.initRecordMp3()

### 2, 开始录制
   
     recordPlugin.start()
     
### 3, 停止录制

     recordPlugin.stop()
     
### 4, 播放

#### 1,播放
     
     recordPlugin.play()
     
#### 2, 暂停和继续播放
       
     recordPlugin.pausePlay();

#### 3, 停止播放
    
     recordPlugin.stopPlay();
          
### 5, 根据传递的路径进行语音录制

     recordPlugin.startByWavPath(wavPath);
     
### 6, 根据传递的路径或则Url进行语音播放

     
      ///
      /// 参数 path  播放音频的地址
      ///
      ///path 为 url类型对应的在线播放地址   https://linjuli-app-audio.oss-cn-hangzhou.aliyuncs.com/audio/50c39c768b534ce1ba25d837ed153824.wav
      ///path 对应本地文件路径对应的是本地文件播放肚子   /sdcard/flutterdemo/wiw.wav
      /// 参数  type
      /// 当path 为url   type为 url
      /// 当path 为本地地址 type为 file
      ///
      Future playByPath(String path, String type) async {
        return await _invokeMethod('playByPath', <String, String>{
          "play": "play",
          "path": path,
          "type": type,
        });
      }   

### 7, 释放资源
可以在页面退出的时候进行资源释放 比如在  dispose方法中调用如下代码

     recordPlugin.dispose()
     
     
     
### 4,回调监听  
1,初始化回调监听  

  
    ///初始化方法的监听
    recordPlugin.responseFromInit.listen((data) {
      if (data) {
        print("初始化成功");
      } else {
        print("初始化失败");
      }
    });
    

2,开始录制停止录制监听

     /// 开始录制或结束录制的监听
        recordPlugin.response.listen((data) {
          if (data.msg == "onStop") {
            ///结束录制时会返回录制文件的地址方便上传服务器
            print("onStop  " + data.path);
          } else if (data.msg == "onStart") {
            print("onStart --");
          }
        });
    
3,录制声音大小回调监听


     ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
        recordPlugin.responseFromAmplitude.listen((data) {
          var voiceData = double.parse(data.msg);
          var tempVoice = "";
          if (voiceData > 0 && voiceData < 0.1) {
            tempVoice = "images/voice_volume_2.png";
          } else if (voiceData > 0.2 && voiceData < 0.3) {
            tempVoice = "images/voice_volume_3.png";
          } else if (voiceData > 0.3 && voiceData < 0.4) {
            tempVoice = "images/voice_volume_4.png";
          } else if (voiceData > 0.4 && voiceData < 0.5) {
            tempVoice = "images/voice_volume_5.png";
          } else if (voiceData > 0.5 && voiceData < 0.6) {
            tempVoice = "images/voice_volume_6.png";
          } else if (voiceData > 0.6 && voiceData < 0.7) {
            tempVoice = "images/voice_volume_7.png";
          } else if (voiceData > 0.7 && voiceData < 1) {
            tempVoice = "images/voice_volume_7.png";
          }
          setState(() {
            voiceIco = tempVoice;
            if(overlayEntry!=null){
              overlayEntry.markNeedsBuild();
            }
          });
    
          print("振幅大小   " + voiceData.toString() + "  " + voiceIco);
        });
    
    
    
    
    
    
    
       
   
4,播放声音完成的监听监听
     
   
      recordPlugin.responsePlayStateController.listen((data){
      print("播放路径   " + data.playPath );
      print("播放状态   " + data.playState );
    });
    
    
    
## 2,录制组件的使用

组件使用效果

android效果

<!--![](.README_images/video2gif_20191118_101627.gif)-->
<img src="README_images/video2gif_20191118_101627.gif" width="400"  align=center />

IOS效果

<!--![](.README_images/ios.gif)-->
<img src="README_images/ios.gif" width="400"  align=center />





### 1,在使用的页面进行导入package

    import 'package:flutter_plugin_record/index.dart';  
        
    
    

    
### 2,在使用的地方引入VoiceWidget组件
    
    VoiceWidget(),
    
    
    VoiceWidget({startRecord: Function, stopRecord: Function}) {
    
   
    
startRecord 开始录制的回调 

stopRecord 停止录制的回调 返回的path是录制成功之后文件的保存地址

    
     
## IOS配置注意事项
 
### ios集成的的时候需要在info.list添加 
 
     
     <key>NSMicrophoneUsageDescription</key>
            <string>打开话筒</string>


     <key>NSAppTransportSecurity</key>
     	<dict>
     		<key>NSAllowsArbitraryLoads</key>
     		<true/>
     	</dict>



### ios release 打包失败配置注意事项

<!--![](.README_images/ios.gif)-->
<img src="README_images/ios_error.png" width="400"  align=center />



 
## android配置注意事项
 
### android 集成的的时候需要在application标签下添加 
 
     
      tools:replace="android:label"
     






  
## TODO

* [ ] 双声道切换 单声道切换

## 感谢


[肖中旺](https://github.com/xzw421771880)对IOS 播放在线Wav的支持 


## 作者的其他开源项目推荐


[基于腾讯云点播封装的flutter版的播放器插件 ](https://github.com/yxwandroid/flutter_tencentplayer_plus)

[Flutter 二维码扫描插件](https://github.com/yxwandroid/flutter_plugin_qrcode)

[抖音开发平台SDK Flutter插件](https://github.com/yxwandroid/flutter_plugin_douyin_open)

[FLutter地图插件](https://github.com/yxwandroid/flutter_amap_location)

[Flutter 模板工程](https://github.com/yxwandroid/flutter_app_redux.git)

## 关注公众号获取更多内容

<img src="README_images/f53502b3.png" width="200" hegiht="313" align=center />



================================================
FILE: android/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures


================================================
FILE: android/build.gradle
================================================
group 'record.wilson.flutter.com.flutter_plugin_record'
version '1.0-SNAPSHOT'

buildscript {
//    ext.kotlin_version = '1.2.71'
    ext.kotlin_version = '1.3.50'

    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

rootProject.allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion 29

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
    defaultConfig {
        minSdkVersion 19
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    lintOptions {
        disable 'InvalidPackage'
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.github.shaoshuai904:RecordWav:1.0.2'
    implementation "androidx.appcompat:appcompat:1.0.0"
    implementation 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'
}


================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip


================================================
FILE: android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M



================================================
FILE: android/settings.gradle
================================================
rootProject.name = 'flutter_plugin_record'


================================================
FILE: android/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="record.wilson.flutter.com.flutter_plugin_record">
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/FlutterPluginRecordPlugin.kt
================================================
package record.wilson.flutter.com.flutter_plugin_record

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.util.Log
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import cafe.adriel.androidaudioconverter.AndroidAudioConverter
import cafe.adriel.androidaudioconverter.callback.IConvertCallback
import cafe.adriel.androidaudioconverter.callback.ILoadCallback
import cafe.adriel.androidaudioconverter.model.AudioFormat
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.Registrar
import record.wilson.flutter.com.flutter_plugin_record.utils.*
import java.io.File
import java.util.*


class FlutterPluginRecordPlugin : FlutterPlugin, MethodCallHandler, ActivityAware ,PluginRegistry.RequestPermissionsResultListener {

    lateinit var channel: MethodChannel
    private lateinit var _result: Result
    private lateinit var call: MethodCall
    private lateinit var voicePlayPath: String
    private var recorderUtil: RecorderUtil? = null
    private var recordMp3:Boolean=false;

    @Volatile
    private var audioHandler: AudioHandler? = null

    lateinit var activity:Activity

    companion object {
        //support embedding v1
        @JvmStatic
        fun registerWith(registrar: Registrar) {
            val plugin = initPlugin(registrar.messenger())
            plugin.activity=registrar.activity()
            registrar.addRequestPermissionsResultListener(plugin)
        }

        private fun initPlugin(binaryMessenger: BinaryMessenger):FlutterPluginRecordPlugin {
            val channel = createMethodChannel(binaryMessenger)
            val plugin = FlutterPluginRecordPlugin()
            channel.setMethodCallHandler(plugin)
            plugin.channel = channel
            return plugin
        }

        private fun createMethodChannel(binaryMessenger: BinaryMessenger):MethodChannel{
            return  MethodChannel(binaryMessenger, "flutter_plugin_record");
        }
    }
    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
       val methodChannel = createMethodChannel(binding.binaryMessenger)
        methodChannel.setMethodCallHandler(this)
        channel=methodChannel
    }



    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        initActivityBinding(binding)
    }

    private fun initActivityBinding(binding: ActivityPluginBinding) {
        binding.addRequestPermissionsResultListener(this)
        activity=binding.activity
    }


    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
        initActivityBinding(binding)
    }

    override fun onDetachedFromActivityForConfigChanges() {
    }
    override fun onDetachedFromActivity() {
    }


    override  fun onMethodCall(call: MethodCall, result: Result) {
        _result = result
        this.call = call
        when (call.method) {
            "init" -> init()
            "initRecordMp3" -> initRecordMp3()
            "start" -> start()
            "startByWavPath" -> startByWavPath()
            "stop" -> stop()
            "play" -> play()
            "pause" -> pause()
            "playByPath" -> playByPath()
            "stopPlay" -> stopPlay()
            else -> result.notImplemented()
        }
    }



    //初始化wav转 MP3
    private fun initWavToMp3(){
        AndroidAudioConverter.load(activity.applicationContext, object : ILoadCallback {
            override fun onSuccess() {
                // Great!
                Log.d("android", "  AndroidAudioConverter onSuccess")
            }

            override fun onFailure(error: Exception) {
                // FFmpeg is not supported by device
                Log.d("android", "  AndroidAudioConverter onFailure")
            }
        })

    }

    private fun initRecord() {
        if (audioHandler != null) {
            audioHandler?.release()
            audioHandler = null
        }
        audioHandler = AudioHandler.createHandler(AudioHandler.Frequency.F_22050)

        Log.d("android voice  ", "init")
        val id = call.argument<String>("id")
        val m1 = HashMap<String, String>()
        m1["id"] = id!!
        m1["result"] = "success"
        channel.invokeMethod("onInit", m1)

    }

    private fun stopPlay() {
        recorderUtil?.stopPlay()
    }
    //暂停播放
    private fun pause() {
        val isPlaying= recorderUtil?.pausePlay()
        val _id = call.argument<String>("id")
        val m1 = HashMap<String, String>()
        m1["id"] = _id!!
        m1["result"] = "success"
        m1["isPlaying"] = isPlaying.toString()
        channel.invokeMethod("pausePlay", m1)
    }

    private fun play() {

        recorderUtil = RecorderUtil(voicePlayPath)
        recorderUtil!!.addPlayStateListener { playState ->
            print(playState)
            val _id = call.argument<String>("id")
            val m1 = HashMap<String, String>()
            m1["id"] = _id!!
            m1["playPath"] = voicePlayPath
            m1["playState"] = playState.toString()
            channel.invokeMethod("onPlayState", m1)
        }
        recorderUtil!!.playVoice()
        Log.d("android voice  ", "play")
        val _id = call.argument<String>("id")
        val m1 = HashMap<String, String>()
        m1["id"] = _id!!
        channel.invokeMethod("onPlay", m1)
    }

    private fun playByPath() {
        val path = call.argument<String>("path")
        recorderUtil = RecorderUtil(path)
        recorderUtil!!.addPlayStateListener { playState ->
            val _id = call.argument<String>("id")
            val m1 = HashMap<String, String>()
            m1["id"] = _id!!
            m1["playPath"] = path.toString();
            m1["playState"] = playState.toString()
            channel.invokeMethod("onPlayState", m1)
        }
        recorderUtil!!.playVoice()

        Log.d("android voice  ", "play")
        val _id = call.argument<String>("id")
        val m1 = HashMap<String, String>()
        m1["id"] = _id!!
        channel.invokeMethod("onPlay", m1)
    }

    @Synchronized
    private fun stop() {
        if (audioHandler != null) {
            if (audioHandler?.isRecording == true) {
                audioHandler?.stopRecord()
            }
        }
        Log.d("android voice  ", "stop")
    }

    @Synchronized
    private fun start() {
        var packageManager = activity.packageManager
        var permission = PackageManager.PERMISSION_GRANTED == packageManager.checkPermission(Manifest.permission.RECORD_AUDIO,activity.packageName)
        if (permission) {
            Log.d("android voice  ", "start")
            //        recorderUtil.startRecord();
            if (audioHandler?.isRecording == true) {
//            audioHandler?.startRecord(null);
                audioHandler?.stopRecord()
            }
            audioHandler?.startRecord(MessageRecordListener())


            val _id = call.argument<String>("id")
            val m1 = HashMap<String, String>()
            m1["id"] = _id!!
            m1["result"] = "success"
            channel.invokeMethod("onStart", m1)
        } else {
            checkPermission()
        }

    }

    @Synchronized
    private fun startByWavPath() {
        var packageManager = activity.packageManager
        var permission = PackageManager.PERMISSION_GRANTED == packageManager.checkPermission(Manifest.permission.RECORD_AUDIO, activity.packageName)
        if (permission) {
            Log.d("android voice  ", "start")
            val _id = call.argument<String>("id")
            val wavPath = call.argument<String>("wavPath")

            if (audioHandler?.isRecording == true) {
                audioHandler?.stopRecord()
            }
            audioHandler?.startRecord(wavPath?.let { MessageRecordListenerByPath(it) })


            val m1 = HashMap<String, String>()
            m1["id"] = _id!!
            m1["result"] = "success"
            channel.invokeMethod("onStart", m1)
        } else {
            checkPermission()
        }

    }


    private fun init() {
        recordMp3=false
        checkPermission()
    }
    private fun initRecordMp3(){
        recordMp3=true
        checkPermission()
        initWavToMp3()
    }

    private fun checkPermission() {
        var packageManager = activity.packageManager
        var permission = PackageManager.PERMISSION_GRANTED == packageManager.checkPermission(Manifest.permission.RECORD_AUDIO,activity.packageName)
        if (permission) {
            initRecord()
        } else {
            initPermission()
        }


    }

    private fun initPermission() {
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) !== PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
        }
    }


    //自定义路径
    private inner class MessageRecordListenerByPath : AudioHandler.RecordListener {
        var wavPath = ""

        constructor(wavPath: String) {
            this.wavPath = wavPath
        }


        override fun onStop(recordFile: File?, audioTime: Double?) {
            if (recordFile != null) {
                voicePlayPath = recordFile.path
                if (recordMp3){

                    val callback: IConvertCallback = object : IConvertCallback {
                        override fun onSuccess(convertedFile: File) {

                            Log.d("android", "  ConvertCallback ${convertedFile.path}")

                            val _id = call.argument<String>("id")
                            val m1 = HashMap<String, String>()
                            m1["id"] = _id!!
                            m1["voicePath"] = convertedFile.path
                            m1["audioTimeLength"] = audioTime.toString()
                            m1["result"] = "success"
                            activity.runOnUiThread { channel.invokeMethod("onStop", m1) }
                        }

                        override fun onFailure(error: java.lang.Exception) {
                            Log.d("android", "  ConvertCallback $error")
                        }
                    }
                    AndroidAudioConverter.with(activity.applicationContext)
                            .setFile(recordFile)
                            .setFormat(AudioFormat.MP3)
                            .setCallback(callback)
                            .convert()

                }else{
                    val _id = call.argument<String>("id")
                    val m1 = HashMap<String, String>()
                    m1["id"] = _id!!
                    m1["voicePath"] = voicePlayPath
                    m1["audioTimeLength"] = audioTime.toString()
                    m1["result"] = "success"
                    activity.runOnUiThread { channel.invokeMethod("onStop", m1) }

                }
            }

        }


        override fun getFilePath(): String {
            return wavPath;
        }

        private val fileName: String
        private val cacheDirectory: File


        init {
            cacheDirectory = FileTool.getIndividualAudioCacheDirectory(activity)
            fileName = UUID.randomUUID().toString()
        }

        override fun onStart() {
            LogUtils.LOGE("MessageRecordListener onStart on start record")
        }

        override fun onVolume(db: Double) {
            LogUtils.LOGE("MessageRecordListener onVolume " + db / 100)
            val _id = call.argument<String>("id")
            val m1 = HashMap<String, Any>()
            m1["id"] = _id!!
            m1["amplitude"] = db / 100
            m1["result"] = "success"

            activity.runOnUiThread { channel.invokeMethod("onAmplitude", m1) }


        }

        override fun onError(error: Int) {
            LogUtils.LOGE("MessageRecordListener onError $error")
        }
    }


    private inner class MessageRecordListener : AudioHandler.RecordListener {
        override fun onStop(recordFile: File?, audioTime: Double?) {
            LogUtils.LOGE("MessageRecordListener onStop $recordFile")
            if (recordFile != null) {
                voicePlayPath = recordFile.path
                if (recordMp3){
                    val callback: IConvertCallback = object : IConvertCallback {
                        override fun onSuccess(convertedFile: File) {

                            Log.d("android", "  ConvertCallback ${convertedFile.path}")

                            val _id = call.argument<String>("id")
                            val m1 = HashMap<String, String>()
                            m1["id"] = _id!!
                            m1["voicePath"] = convertedFile.path
                            m1["audioTimeLength"] = audioTime.toString()
                            m1["result"] = "success"
                            activity.runOnUiThread { channel.invokeMethod("onStop", m1) }
                        }

                        override fun onFailure(error: java.lang.Exception) {
                            Log.d("android", "  ConvertCallback $error")
                        }
                    }
                    AndroidAudioConverter.with(activity.applicationContext)
                            .setFile(recordFile)
                            .setFormat(AudioFormat.MP3)
                            .setCallback(callback)
                            .convert()

                }else{
                    val _id = call.argument<String>("id")
                    val m1 = HashMap<String, String>()
                    m1["id"] = _id!!
                    m1["voicePath"] = voicePlayPath
                    m1["audioTimeLength"] = audioTime.toString()
                    m1["result"] = "success"
                    activity.runOnUiThread { channel.invokeMethod("onStop", m1) }

                }
            }

        }


        override fun getFilePath(): String {
            val file = File(cacheDirectory, fileName)
            return file.absolutePath
        }

        private val fileName: String
        private val cacheDirectory: File


        init {
            cacheDirectory = FileTool.getIndividualAudioCacheDirectory(activity)
            fileName = UUID.randomUUID().toString()
        }

        override fun onStart() {
            LogUtils.LOGE("MessageRecordListener onStart on start record")
        }

        override fun onVolume(db: Double) {
            LogUtils.LOGE("MessageRecordListener onVolume " + db / 100)
            val _id = call.argument<String>("id")
            val m1 = HashMap<String, Any>()
            m1["id"] = _id!!
            m1["amplitude"] = db / 100
            m1["result"] = "success"

            activity.runOnUiThread { channel.invokeMethod("onAmplitude", m1) }


        }

        override fun onError(error: Int) {
            LogUtils.LOGE("MessageRecordListener onError $error")
        }
    }




    // 权限监听回调
    override fun onRequestPermissionsResult(p0: Int, p1: Array<out String>?, p2: IntArray?): Boolean {
        if (p0 == 1) {
            if (p2?.get(0) == PackageManager.PERMISSION_GRANTED) {
//                initRecord()
                return true
            } else {

                Toast.makeText(activity, "Permission Denied", Toast.LENGTH_SHORT).show()
                DialogUtil.Dialog(activity, "申请权限")
            }
            return false
        }

        return false
    }

    

}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimer.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.timer;



public interface ITimer {
    void startTimer();

    void pauseTimer();

    void resumeTimer();

    void stopTimer();
}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimerChangeCallback.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.timer;

public interface ITimerChangeCallback {
    void onTimeChange(long time);
}

================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/MTimer.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.timer;

import android.text.TextUtils;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;

public class MTimer implements ITimer {
    //--real timer--
    private Timer timer;
    //--default task--
    private TimerTask task;
    //--default initdelay--
    private long initDelay = 0l;
    //--default delay--
    private long delay = 0l;
    //--call back--
    private ITimerChangeCallback[] callbacks = null;
    //--real time--
    private AtomicLong time;//时间的记录工具

    //--action--
    private static final int START = 0;
    private static final int PAUSE = 1;
    private static final int RESUME = 2;
    private static final int STOP = 3;

    private int status = STOP;//默认是stop

    private MTimer(long initDelay, long delay, ITimerChangeCallback[] callbacks) {
        this.initDelay = initDelay;
        this.delay = delay;
        this.callbacks = callbacks;
    }

    //-----------------外部方法------------------------

    /**
     * 用于生成MTimer 对象
     *
     * @return --MTimer
     */
    public static Builder makeTimerBuilder() {
        return new Builder();
    }

    /**
     * 开启 timer
     */
    @Override
    public void startTimer() {
        //判断当前是不是stop,是的话开始运行
        if (status != STOP) {
            return;
        }
        //切换当前状态为 start
        status = START;
        realStartTimer(true);
    }

    /**
     * 暂停timer
     */
    @Override
    public void pauseTimer() {
        //判断当前是不是start 是不是resume,如果是其中一个就可以
        if (status != START && status != RESUME) {
            return;
        }
        //切换当前状态为 pause
        status = PAUSE;
        realStopTimer(false);
    }

    /**
     * 重启timer
     */
    @Override
    public void resumeTimer() {
        //判断当前是不是pause ,如果是则恢复
        if (status != PAUSE) {
            return;
        }
        //切换当前状态为 resume
        status = RESUME;
        realStartTimer(false);
    }

    /**
     * 关闭timer
     */
    @Override
    public void stopTimer() {
        //无论当前处于那种状态都可以stop
        status = STOP;
        realStopTimer(true);
    }

    //-----------------内部方法------------------------

    /**
     * timer 真正的开始方法
     *
     * @param isToZero --是否清除数据
     */
    private void realStartTimer(boolean isToZero) {
        //清空记录时间
        if (isToZero) {
            time = new AtomicLong(0);
        }
        //重新生成timer、task
        if (timer == null && task == null) {
            timer = new Timer();
            task = createTask();
            timer.scheduleAtFixedRate(task, initDelay, delay);
        }
    }

    /**
     * timer 真正的关闭方法
     *
     * @param isToZero --是否清除数据
     */
    private void realStopTimer(boolean isToZero) {
        //清空记录时间
        if (isToZero) {
            time = new AtomicLong(0);
        }
        //关闭当前的timer
        if (timer != null) {
            timer.purge();
            timer.cancel();
            timer = null;

        }
        //关闭当前任务
        if (task != null) {
            task.cancel();
            task = null;
        }
    }


    /**
     * 判断是否设置监听回调
     *
     * @return -- true 表示设置了回调,反之表示没设置
     */
    private boolean checkCallback() {
        return callbacks != null && callbacks.length > 0;
    }

    /**
     * 创建task
     *
     * @return
     */
    private TimerTask createTask() {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                time.incrementAndGet();
                notifyCallback(time);
            }
        };
        return task;
    }

    /**
     * 通知callback
     *
     * @param time --间距走的次数(花费时间=次数*delay+initDelay)
     */
    private void notifyCallback(AtomicLong time) {
        if (checkCallback()) {
            for (ITimerChangeCallback callback : callbacks) {
                callback.onTimeChange(time.longValue());
            }
        }
    }

    public static class Builder {

        //--default initdelay--
        private long initDelay = 0l;
        //--default delay--
        private long delay = 0l;
        //--call back--
        private ITimerChangeCallback[] callbacks = null;
        //--tag--
        private String tag;

        public Builder setTag(String tag) {
            if (TextUtils.isEmpty(tag)) {
                throw new NullPointerException("设置的tag无效!=>setTag(String tag)");
            }
            this.tag = tag;
            return this;
        }


        /**
         * 设置执行当前任务的时候首次执行时的延迟时间
         *
         * @param initDelay --首次执行的延迟时间(ms)
         */
        public Builder setInitDelay(long initDelay) {
            this.initDelay = initDelay;
            return this;
        }

        /**
         * 设置时间回调
         *
         * @param callbacks
         */
        public Builder setCallbacks(ITimerChangeCallback... callbacks) {
            this.callbacks = callbacks;
            return this;
        }

        /**
         * 设置后续的延迟时间
         *
         * @param delay --后续延迟时间(ms)
         */
        public Builder setDelay(long delay) {
            this.delay = delay;
            return this;
        }

        /**
         * 外部会重用此对象,所以需要重置其参数
         */
        public void reset() {
            tag = null;
            initDelay = 0l;
            delay = 0l;
            callbacks = null;
        }

        /**
         * 最终的生成方法,如果不调用此处,timer无法运行
         */
        public MTimer build() {
            //--check delay--
            if (initDelay < 0 || delay < 0) {
                throw new AssertionError("initDelay或delay 不允许小于0");
            }
            //--build timer--
            MTimer timer = new MTimer(initDelay, delay, callbacks);
            //--add to cache--
            if (!TextUtils.isEmpty(tag)) {
                TimerUtils.addTimerToCache(tag, timer);
            }
            //--return timer--
            return timer;
        }

    }
}

================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/TimerUtils.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.timer;

import android.text.TextUtils;
import android.util.Log;

import java.util.Locale;
import java.util.WeakHashMap;
/**
 * description: 定时器工具类
 * author: Simon
 * created at 2017/8/10 上午9:47
 */

public final class TimerUtils {
    //--tag--
    private static final String TAG = "TimerUtils";
    //--err info--
    private static final String ERR_INFO = "未找到对应的MTimer,确认是否设置过Tag!=>new Builder().setTag(String tag)";
    //--cache--
    private static WeakHashMap<String, MTimer> cacheTimerMap = new WeakHashMap<>();
    //--action--
    private static final int START = 0;
    private static final int PAUSE = 1;
    private static final int RESUME = 2;
    private static final int STOP = 3;

    //--recycle build--
    private static final MTimer.Builder BUILDER = new MTimer.Builder();

    private TimerUtils() {
        throw new AssertionError("you can't init me!");
    }

    /**
     * 注意此方法,会重复利用Builder 对象,所以每次build()完成后再重新使用该方法!!
     *
     * @return --builder
     */
    public static MTimer.Builder makeBuilder() {
        BUILDER.reset();//每次执行的时候都会重置一次
        return BUILDER;
    }

    /**
     * 开启timer ,时间清零
     */
    public static void startTimer(String tag) {
        actionTimer(START, tag);
    }

    /**
     * 恢复timer,不清零
     */
    public static void resumeTimer(String tag) {
        actionTimer(RESUME, tag);
    }

    /**
     * 暂停timer
     */
    public static void pauseTimer(String tag) {
        actionTimer(PAUSE, tag);
    }

    /**
     * 关闭 timer
     */
    public static void stopTimer(String tag) {
        actionTimer(STOP, tag);
    }

    /**
     * 格式化 时间 格式为  hh:mm:ss
     *
     * @param cnt
     * @return
     */
    public static String formatTime(long cnt) {
        long hour = cnt / 3600;
        long min = cnt % 3600 / 60;
        long second = cnt % 60;
        return String.format(Locale.CHINA, "%02d:%02d:%02d", hour, min, second);
    }

    //------------------------私有方法/内部类------------------------------

    /**
     * 添加timer到缓存
     *
     * @param tag   --tag
     * @param timer --timer
     */
    public static void addTimerToCache(String tag, MTimer timer) {
        if (cacheTimerMap == null) {
            cacheTimerMap = new WeakHashMap<>();
        }
        cacheTimerMap.put(tag, timer);
    }

    /**
     * 真正的执行方法
     *
     * @param action --行为
     * @param tag    --tag
     */
    private static void actionTimer(int action, String tag) {
        //-----check tag----
        if (!checkTag(tag)) {
            Log.e(TAG, "The tag is empty or null!");
            return;
        }
        //-----check timer----
        MTimer timer = findMTimerByTag(tag);
        if (timer == null) {
            Log.e(TAG, "Can't found timer by tag!");
            return;
        }

        //-----action timer----
        switch (action) {
            case START:
                timer.startTimer();
                break;
            case RESUME:
                timer.resumeTimer();
                break;
            case PAUSE:
                timer.pauseTimer();
                break;
            case STOP:
                timer.stopTimer();
                break;
        }


    }


    /**
     * 通过tag获取mtimer
     *
     * @param tag --设置的tag
     * @return --MTimer
     */
    private static MTimer findMTimerByTag(String tag) {
        if (!checkTag(tag) || cacheTimerMap == null || cacheTimerMap.size() == 0) {//tag无效,没有缓存数据,返回null
            return null;
        } else {//反之根据tag返回
            return cacheTimerMap.get(tag);
        }
    }

    /**
     * 判断tag 是否有效
     *
     * @param tag --tag
     * @return true表示有效,反之无效
     */
    private static boolean checkTag(String tag) {
        return !TextUtils.isEmpty(tag);
    }


}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/AudioHandler.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Locale;

import record.wilson.flutter.com.flutter_plugin_record.timer.ITimerChangeCallback;
import record.wilson.flutter.com.flutter_plugin_record.timer.TimerUtils;
public final class AudioHandler extends Handler {

    private static final String TAG = "AudioHandler";

    private final        WeakReference<AudioThread> mThread;
    private static final int                        MESSAGE_START_RECORD      = 0X01;
    private static final int                        MESSAGE_PAUSE_RECORD      = 0X02;
    private static final int                        MESSAGE_STOP_RECORD       = 0X03;
    private static final int                        MESSAGE_SAVE_RECORD       = 0X04;
    private static final int                        MESSAGE_ADD_LISTENER      = 0X05;
    private static final int                        MESSAGE_REMOVE_LISTENER   = 0X06;
    private static final int                        MESSAGE_RELEASE           = 0X07;
    private static final int                        MESSAGE_GET_LATEST_RECORD = 0X08;

    public static final int MAX_DB = 96;

    public static final int STATE_AUDIO_RECORD_PREPARING = 0X01;
    public static final int STATE_AUDIO_RECORD_START     = 0X02;
    public static final int STATE_AUDIO_RECORD_PAUSE     = 0X03;
    public static final int STATE_AUDIO_RECORD_STOPPED   = 0X04;

    public static AudioHandler createHandler(Frequency frequency) {
        AudioThread thread = new AudioThread(frequency);
        thread.start();
        return thread.getHandler();
    }

    private AudioHandler(AudioThread thread) {
        mThread = new WeakReference<AudioThread>(thread);
    }

    public void startRecord(RecordListener listener) {
        if (isRecording()) stopRecord();
        Message message = obtainMessage(MESSAGE_START_RECORD);
        message.obj = listener;
        sendMessage(message);
    }

    public boolean isAvailable() {
        AudioThread audioThread = mThread.get();
        return audioThread != null && audioThread.isAvailable;
    }

    public void stopRecord() {
        AudioThread audioThread = mThread.get();
        if (audioThread != null) audioThread.setPauseRecord();
    }

    public void cancelRecord() {
        AudioThread audioThread = mThread.get();
        if (audioThread != null) audioThread.setCancelRecord();
    }

    public boolean isRecording() {
        AudioThread audioThread = mThread.get();
        return audioThread != null && audioThread.isRecording();
    }

    public void release() {
        AudioThread audioThread = mThread.get();
        if (audioThread != null) audioThread.release();
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        AudioThread audioThread = mThread.get();
        if (audioThread == null) return;
        switch (msg.what) {
            case MESSAGE_START_RECORD:
                Object obj = msg.obj;
                RecordListener listener = null;
                if (obj instanceof RecordListener)
                    listener = (RecordListener) obj;
                audioThread.startRecord(listener);
                break;
            default:
                break;
        }
    }

    public enum Frequency {
        F_44100(44100),
        F_22050(22050),
        F_16000(16000),
        F_11025(11025),
        F_8000(8000);
        private int f;

        private Frequency(int f) {
            this.f = f;
        }

        public int getFrequency() {
            return f;
        }

    }

    private static final class AudioThread extends Thread {

        private static final int[] FREQUENCY = {
                44100,
                22050,
                16000,
                11025,
                8000
        };
        private final        int   mPriority;

        private       AudioHandler     mHandler;
        private       boolean          isAvailable;
        private final Object           sync        = new Object();
        private       int              mFrequency;
        private       int              channel     = AudioFormat.CHANNEL_IN_MONO;
        private       int              audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        private       int              bufferSize;
        private       AudioRecord      mRecord;
        private       SimpleDateFormat format;
        private       int              mTid;
        private       Looper           mLooper;

        private volatile boolean isCancel;
        private String tag = "AudioTimerTag";

        private double audioTime = 0;  //录音时长

        //--设置 tag 后可以通过 tag 操作--
        private void initTimer() {
            TimerUtils.makeBuilder().setTag(tag).setInitDelay(0).setDelay(100).setCallbacks(new ITimerChangeCallback() {
                @Override
                public void onTimeChange(long time) {
                    //Log.v("AudioTimerTag", time + "--> AudioTimer");
                    audioTime = time / 10.0;
                }
            }).build();

        }

        private AudioThread(Frequency frequency) {
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
            mFrequency = frequency.getFrequency();
            isAvailable = checkSampleRatesValid(mFrequency);
            Log.e(TAG, "FREQUENCY " + mFrequency);
            format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
            if (isAvailable) {
                bufferSize = AudioRecord.getMinBufferSize(mFrequency, channel, audioFormat);
                Log.e(TAG, String.format("buffer size %d", bufferSize));
            }
            initTimer();
        }

        boolean isRecording() {
            if (mRecord == null) {
                return false;
            }
            if (mRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
                return false;
            }
            return mRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;
        }

        private volatile boolean isRecording;

        private void startRecord(RecordListener listener) {

            audioTime =0;
            TimerUtils.startTimer(tag);
            Log.e(TAG, "call start record");
            if (!isAvailable) {
                return;
            }
            byte[] buffer = new byte[bufferSize];
            pauseRecord();
            if (mRecord == null) {
                mRecord = new AudioRecord(MediaRecorder.AudioSource.MIC
                        , mFrequency
                        , channel
                        , audioFormat
                        , bufferSize * 10);
            }

            if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) {
                pauseRecord();
                return;
            }

            BufferedOutputStream out = null;
            File recordFile = null;
            if (listener != null) {
                String filePath = listener.getFilePath();
                if (!filePath.endsWith(".wav"))
                    filePath = filePath + ".wav";
                recordFile = new File(filePath);
            }
            try {
                if (listener != null) {
                    if (!recordFile.createNewFile()) {
                        listener.onError(-20);
                        pauseRecord();
                        return;
                    }
                    out = new BufferedOutputStream(new FileOutputStream(recordFile));
                    WaveHeaderHelper.writeHeader(out, mFrequency, 16, 1);
                }
                mRecord.startRecording();
                isCancel = false;
                if (listener != null) listener.onStart();
                Log.e(TAG, "start recording");
                isRecording = true;
//                Log.d(TAG, "BUFFER LIMIT IS " + buffer.limit() + "\n\t\t\tCAPACITY IS" + buffer.capacity());
                long length = 0;
                boolean turn = false;
                while (isRecording && mRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                    int read = mRecord.read(buffer, 0, buffer.length);
                    if (read < 0) {
                        Log.e(TAG, read == -3 ? "ERROR_INVALID_OPERATION"
                                : read == -2 ? "ERROR_BAD_VALUE"
                                : read == -1 ? "ERROR"
                                : String.valueOf(read));
                        if (listener != null) listener.onError(read);
                        break;
                    }
                    if (out != null) {
                        out.write(buffer, 0, read);
                    }
                    if (listener != null) {
                        if (turn) {
                            listener.onVolume(getDb(buffer));
                        }
                        turn = !turn;
                    }
                    length += read;
                }

            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                pauseRecord();
                FileTool.closeIO(out);
            }
            if (listener != null) {
                WaveHeaderHelper.writeWaveHeaderLength(recordFile);
                if (isCancel) {
                    recordFile.deleteOnExit();
                    recordFile = null;
                }
                TimerUtils.stopTimer(tag);
//                listener.onStop(recordFile);
                listener.onStop(recordFile, audioTime);
            }
        }

        public void setPauseRecord() {
            isRecording = false;
        }

        public void setCancelRecord() {
            isCancel = true;
            setPauseRecord();
        }

        private double getDb(byte[] buffer) {
            double diviation = getDiviation(buffer, 0, buffer.length);
            return 20 * Math.log10(diviation);

        }

        private static short getShort(byte argB1, byte argB2) {
            //if (ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) {
            //    return (short) ((argB1 << 8) | argB2);
            //}
            return (short) (argB1 | (argB2 << 8));
        }

        private static double getDiviation(byte[] buffer, int start, int length) {
            if (0 != (length % 2)) {
                length--;
            }
            double[] divArray = new double[length];
//        short[] array = ByteBuffer.wrap(buffer, start, length).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().array();
            for (int i = start; i < start + length; i += 2) {
                divArray[i / 2] = getShort(buffer[i], buffer[i + 1]) * 1.0;
            }
            return StandardDiviation(divArray);
        }

        //标准差σ=sqrt(s^2)
        private static double StandardDiviation(double[] x) {
            int m = x.length;
            double sum = 0;
            for (int i = 0; i < m; i++) {//求和
                sum += x[i];
            }
            double dAve = sum / m;//求平均值
            double dVar = 0;
            for (int i = 0; i < m; i++) {//求方差
                double v = x[i] - dAve;
                dVar += v * v;
            }
            return Math.sqrt(dVar / m);
        }

        private void pauseRecord() {
            setPauseRecord();
            if (mRecord != null) {
                if (mRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
                    mRecord.release();
                    mRecord = null;
                }
                if (mRecord != null && mRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                    mRecord.stop();
                    mRecord.release();
                    mRecord = null;
                }
            }
        }

        public void release() {
            pauseRecord();
            synchronized (sync) {
                if (Looper.myLooper() != Looper.getMainLooper()) {
                    if (mLooper != null) {
                        mLooper.quit();
                    }
                }
            }
        }

        @Override
        public void run() {
            mTid = Process.myTid();
            Log.e(TAG, "thread start running");
            Looper.prepare();
            synchronized (sync) {
                mLooper = Looper.myLooper();
                mHandler = new AudioHandler(this);
                sync.notifyAll();
            }
            Process.setThreadPriority(mPriority);
            Looper.loop();
            synchronized (sync) {
                mHandler = null;
                sync.notifyAll();
            }
            mTid = -1;
        }

        AudioHandler getHandler() {
            if (!isAlive()) {
                return null;
            }
            synchronized (sync) {
                while (isAlive() && mHandler == null) {
                    try {
                        sync.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            return mHandler;
        }

        public boolean checkSampleRatesValid(int frequency) {
            int bufferSize = AudioRecord.getMinBufferSize(frequency,
                    AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
            return bufferSize > 0;
        }

        public int getValidSampleRates() {
            for (int i = 0; i < FREQUENCY.length; i++) {
                int bufferSize = AudioRecord.getMinBufferSize(FREQUENCY[i],
                        AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
                if (bufferSize > 0) {
                    return FREQUENCY[i];
                }
            }
            return -1;
        }

    }

    public interface RecordListener {
        void onStart();

        String getFilePath();

        void onVolume(double db);

//        void onStop(File recordFile);
        void onStop(File recordFile,Double audioTime);
        void onError(int error);
    }


    private static class WaveHeaderHelper {

        private static void writeHeader(OutputStream out, int sampleRate, int encoding, int channel) throws IOException {
            writeString(out, "RIFF"); // chunk id
            writeInt(out, 0); // chunk size
            writeString(out, "WAVE"); // format
            writeString(out, "fmt "); // subchunk 1 id
            writeInt(out, 16); // subchunk 1 size
            writeShort(out, (short) 1); // audio format (1 = PCM)
            writeShort(out, (short) channel); // number of channels
            writeInt(out, sampleRate); // sample rate
            writeInt(out, sampleRate * channel * encoding >> 3); // byte rate
            writeShort(out, (short) (channel * encoding >> 3)); // block align
            writeShort(out, (short) encoding); // bits per sample
            writeString(out, "data"); // subchunk 2 id
            writeInt(out, 0); // subchunk 2 size
        }

        private static void writeWaveHeaderLength(File f) {
            RandomAccessFile raf = null;
            try {
                raf = new RandomAccessFile(f, "rw");
                long length = f.length();
                long chunkSize = length - 8;
                long subChunkSize = length - 44;
                raf.seek(4);
                raf.write((int) (chunkSize >> 0));
                raf.write((int) (chunkSize >> 8));
                raf.write((int) (chunkSize >> 16));
                raf.write((int) (chunkSize >> 24));
                raf.seek(40);
                raf.write((int) (subChunkSize >> 0));
                raf.write((int) (subChunkSize >> 8));
                raf.write((int) (subChunkSize >> 16));
                raf.write((int) (subChunkSize >> 24));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                FileTool.closeIO(raf);
            }
        }

        private static void writeInt(final OutputStream output, final int value) throws IOException {
            output.write(value >> 0);
            output.write(value >> 8);
            output.write(value >> 16);
            output.write(value >> 24);
        }

        private static void writeShort(final OutputStream output, final short value) throws IOException {
            output.write(value >> 0);
            output.write(value >> 8);
        }

        private static void writeString(final OutputStream output, final String value) throws IOException {
            for (int i = 0; i < value.length(); i++) {
                output.write(value.charAt(i));
            }
        }
    }

}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DateUtils.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtils {
    public static String dateToString(Date date) {
        String str = "yyyyMMddhhmmss";
        SimpleDateFormat format = new SimpleDateFormat(str);
        String dateFormat = format.format(date);
        return dateFormat;
    }
}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DialogUtil.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.app.AlertDialog;
/**
 * 对话框管理
 * 打开app应用程序信息界面
 */
public class DialogUtil {

    public static void Dialog(final Activity activity, String content) {
         Dialog deleteDialog = new AlertDialog.Builder(activity)
                .setTitle("提示")
                .setMessage("请进入应用信息界面开启录音权限")
                .setPositiveButton("确定",
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                startSetting(activity);
                            }
                        })
                .create();
        deleteDialog.setCanceledOnTouchOutside(false);
        deleteDialog.setCancelable(false);
        deleteDialog.show();
    }


    /**
     * 启动app设置应用程序信息界面
     */
    public static void startSetting(Context context) {
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        if (Build.VERSION.SDK_INT >= 9) {
            intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
            intent.setData(Uri.fromParts("package", context.getPackageName(), null));
        } else if (Build.VERSION.SDK_INT <= 8) {
            intent.setAction(Intent.ACTION_VIEW);
            intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
            intent.putExtra(context.getPackageName(), context.getPackageName());
        }
        context.startActivity(intent);
    }
}



================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/FileTool.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.ExifInterface;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static android.os.Environment.MEDIA_MOUNTED;

public final class FileTool {

    private FileTool() {
    }

    public static final String FILE_TYPE_PDF = "pdf";

    public static final String FILE_TYPE_APK = "vnd.android.package-archive";

    public static final String FILE_TYPE_EXCEL  = "ms-excel";
    public static final String FILE_TYPE_EXCELX = "vnd.openxmlformats-officedocument.spreadsheetml";

    public static final String FILE_TYPE_PPT  = "powerpoint";
    public static final String FILE_TYPE_PPTX = "vnd.openxmlformats-officedocument.presentationml";

    public static final String FILE_TYPE_WORD  = "word";
    public static final String FILE_TYPE_WORDX = "vnd.openxmlformats-officedocument.wordprocessingml";

    public static final String FILE_TYPE_RAR   = "rar";
    public static final String FILE_TYPE_ZIP   = "zip";
    public static final String FILE_TYPE_AUDIO = "audio";
    public static final String FILE_TYPE_TEXT  = "text";
    public static final String FILE_TYPE_XML   = "xml";
    public static final String FILE_TYPE_HTML  = "html";
    public static final String FILE_TYPE_IMAGE = "image";
    public static final String FILE_TYPE_VIDEO = "video";
    public static final String FILE_TYPE_APP   = "application";

    /**
     * @Fields maxFileSize : 最大允许文件大小
     **/
    private static final int MAX_FILE_SIZE = 2 * 1024 * 1024;

    public static byte[] GetFileDataBytes(File file, int fileLen) {
        ByteArrayOutputStream bos = null;
        BufferedInputStream in = null;
        try {
            bos = new ByteArrayOutputStream(fileLen);
            in = new BufferedInputStream(new FileInputStream(file));
            int buf_size = 1024;
            byte[] buffer = new byte[buf_size];
            int len = 0;
            while (-1 != (len = in.read(buffer, 0, buf_size))) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeIO(bos, in);
        }
        return null;
    }

    public static String getFileRootPathString(Context context) {
        return new String(getFileRootPath(context).getAbsolutePath());

    }

    public static String getDefaultApkSavePath() {
        return new StringBuffer(Environment.getExternalStorageDirectory().getAbsolutePath()).append(File.separator).append(Environment.DIRECTORY_DOWNLOADS).toString();
    }


    public static File getFileRootPath(Context context) {
        File file = null;
        if (context == null) return null;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            file = context.getExternalCacheDir();
        }
        if (file == null) {
            file = context.getCacheDir();
        }
        return file;
    }

//	public static String getHeadImagePathString(Context mContext) {
//		return getHeadImagePath(mContext).getAbsolutePath();
//	}

    public static void copyFile2(String oldFilePath, String newFilePath) {
        FileOutputStream fs = null;
        FileInputStream inStream = null;
        try {
            fs = new FileOutputStream(newFilePath);
            inStream = new FileInputStream(oldFilePath);
            int byteread = 0;
            if (inStream.available() <= MAX_FILE_SIZE) {
                byte[] buffer = new byte[1024];
                while ((byteread = inStream.read(buffer)) != -1) {
                    fs.write(buffer, 0, byteread);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeIO(fs, inStream);
        }
    }

    public static boolean isValidName(String text) {
        Pattern pattern = Pattern.compile(
                "# Match a valid Windows filename (unspecified file system).          \n" +
                        "^                                # Anchor to start of string.        \n" +
                        "(?!                              # Assert filename is not: CON, PRN, \n" +
                        "  (?:                            # AUX, NUL, COM1, COM2, COM3, COM4, \n" +
                        "    CON|PRN|AUX|NUL|             # COM5, COM6, COM7, COM8, COM9,     \n" +
                        "    COM[1-9]|LPT[1-9]            # LPT1, LPT2, LPT3, LPT4, LPT5,     \n" +
                        "  )                              # LPT6, LPT7, LPT8, and LPT9...     \n" +
                        "  (?:\\.[^.]*)?                  # followed by optional extension    \n" +
                        "  $                              # and end of string                 \n" +
                        ")                                # End negative lookahead assertion. \n" +
                        "[^<>:\"/\\\\|?*\\x00-\\x1F]*     # Zero or more valid filename chars.\n" +
                        "[^<>:\"/\\\\|?*\\x00-\\x1F\\ .]  # Last char is not a space or dot.  \n" +
                        "$                                # Anchor to end of string.            ",
                Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.COMMENTS);
        Matcher matcher = pattern.matcher(text);
        boolean isMatch = matcher.matches();
        return isMatch;
    }

    public static void deleteFile(File f) {
        if (f.exists()) {
            if (f.isDirectory()) {
                File[] files = f.listFiles();
                for (int i = 0; i < files.length; i++) {
                    deleteFile(files[i]);
                }
            }
            f.delete();
        }
    }

    public static String getFileMD5String(File f) {
        byte[] buffer = new byte[4 * 1024];
        BufferedInputStream bis = null;
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            bis = new BufferedInputStream(new FileInputStream(f));
            int lent;
            while ((lent = bis.read(buffer)) != -1) {
                digest.update(buffer, 0, lent);
            }
            byte[] hash = digest.digest();
            StringBuilder hex = new StringBuilder(hash.length * 2);
            for (byte b : hash) {
                if ((b & 0xFF) < 0x10) hex.append("0");
                hex.append(Integer.toHexString(b & 0xFF));
            }
            return hex.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } finally {
            FileTool.closeIO(bis);
        }
        return null;
    }

    public static String md5(String string) {
        byte[] hash;
        try {
            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Huh, MD5 should be supported?", e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Huh, UTF-8 should be supported?", e);
        }

        StringBuilder hex = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xFF) < 0x10) hex.append("0");
            hex.append(Integer.toHexString(b & 0xFF));
        }
        return hex.toString();
    }

    /**
     * <p>
     * 判断照片角度
     * </p>
     *
     * @param path @return int @throws
     */
    public static int readPictureDegree(String path) {
        int degree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return degree;
    }

    public static String getFileExtension(File file) {
        String fileName = file.getName();
        return getFileExtension(fileName);
    }

    /**
     * 获取文件扩展名
     *
     * @param fileName
     * @return
     */
    public static String getFileExtension(String fileName) {

        if (fileName.lastIndexOf(".") != -1 && fileName.lastIndexOf(".") != 0) {
            return fileName.substring(fileName.lastIndexOf(".") + 1);
        } else {
            return "";
        }
    }

    public static String urlDelStoken(String url) {
        url = url.trim().toLowerCase();
        // 是否为http并且是否包含Stoken
        if (!(url.startsWith("http") && url.contains("stoken"))) {
            return url;
        }
        String[] tampStrings = url.split("[?&]");
        for (String temp : tampStrings) {
            if (temp.trim().startsWith("stoken=")) {
                url = url.replace(temp, "").replace("?&", "?");
                break;
            }
        }
        return url;
    }

    /*******************************************************************************
     * Copyright 2011-2014 Sergey Tarasevich
     * <p/>
     * Licensed under the Apache License, Version 2.0 (the "License"); you may
     * not use this file except in compliance with the License. You may obtain a
     * copy of the License at
     * <p/>
     * http://www.apache.org/licenses/LICENSE-2.0
     * <p/>
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     * License for the specific language governing permissions and limitations
     * under the License.
     *******************************************************************************/

    private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
    private static final String INDIVIDUAL_DIR_NAME         = "uil-images";
    private static final String INDIVIDUAL_LOG_FOLDER        = "logs";
    private static final String INDIVIDUAL_UPLOAD_CACHE     = "upload-cache";
    private static final String FILE_DIR_NAME               = "file_cache";
    private static final String FILE_AUDIO_DIR_NAME         = "Audio";
    private static final String FILE_IMAGE_DIR_NAME         = "image";

    /**
     * Returns application cache directory. Cache directory will be created on
     * SD card <i>("/Android/data/[app_package_name]/cache")</i> if card is
     * mounted and app has appropriate permission. Else - Android defines cache
     * directory on device's file system.
     *
     * @return Cache {@link File directory}.<br />
     * <b>NOTE:</b> Can be null in some unpredictable cases (if SD card
     * is unmounted and {@link Context#getCacheDir()
     * Context.getCacheDir()} returns null).
     */
    public static File getCacheDirectory(Context context) {
        return getCacheDirectory(context, true);
    }

    /**
     * Returns application cache directory. Cache directory will be created on
     * SD card <i>("/Android/data/[app_package_name]/cache")</i> (if card is
     * mounted and app has appropriate permission) or on device's file system
     * depending incoming parameters.
     *
     * @param preferExternal Whether prefer external location for cache
     * @return Cache {@link File directory}.<br />
     * <b>NOTE:</b> Can be null in some unpredictable cases (if SD card
     * is unmounted and {@link Context#getCacheDir()
     * Context.getCacheDir()} returns null).
     */
    public static File getCacheDirectory(Context context, boolean preferExternal) {
        assert context != null;
        File appCacheDir = null;
        String externalStorageState;
        try {
            externalStorageState = Environment.getExternalStorageState();
        } catch (NullPointerException e) { // (sh)it happens (Issue #660)
            externalStorageState = "";
        } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue
            // #989)
            externalStorageState = "";
        }
        if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {
            appCacheDir = getExternalCacheDir(context);
        }
        if (appCacheDir == null) {
            appCacheDir = context.getCacheDir();
        }
        if (appCacheDir == null) {
            String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
            appCacheDir = new File(cacheDirPath);
        }
        return appCacheDir;
    }

    /**
     * Returns individual application cache directory (for only image caching
     * from ImageLoader). Cache directory will be created on SD card
     * <i>("/Android/data/[app_package_name]/cache/uil-images")</i> if card is
     * mounted and app has appropriate permission. Else - Android defines cache
     * directory on device's file system.
     *
     * @return Cache {@link File directory}
     */
    public static File getIndividualImageCacheDirectory(Context context) {
        return getIndividualCacheDirectory(context, INDIVIDUAL_DIR_NAME);
    }

    public static File getIndividualUploadCacheDirectory(Context context) {
        return getIndividualCacheDirectory(context, INDIVIDUAL_UPLOAD_CACHE);
    }

    public static File getIndividualLogCacheDirectory(Context context) {
        return getIndividualCacheDirectory(context, INDIVIDUAL_LOG_FOLDER);
    }


    public static File getIndividualAudioCacheDirectory(Context context) {
        File file = new File(getIndividualCacheDirectory(context, FILE_DIR_NAME), FILE_AUDIO_DIR_NAME);
        if(!file.exists()) file.mkdirs();
        return file;
    }

    public static File getIndividualImageFileDirectory(Context context) {
        return new File(getIndividualCacheDirectory(context, FILE_DIR_NAME), FILE_IMAGE_DIR_NAME);
    }

    /**
     * Returns individual application cache directory (for only image caching
     * from ImageLoader). Cache directory will be created on SD card
     * <i>("/Android/data/[app_package_name]/cache/uil-images")</i> if card is
     * mounted and app has appropriate permission. Else - Android defines cache
     * directory on device's file system.
     *
     * @param cacheDir Cache directory path (e.g.: "AppCacheDir",
     *                 "AppDir/cache/images")
     * @return Cache {@link File directory}
     */
    public static File getIndividualCacheDirectory(Context context, String cacheDir) {
        File appCacheDir = getCacheDirectory(context);
        File individualCacheDir = new File(appCacheDir, cacheDir);
        if (!individualCacheDir.exists()) {
            if (!individualCacheDir.mkdir()) {
                individualCacheDir = appCacheDir;
            }
            try {
                new File(individualCacheDir, ".nomedia").createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return individualCacheDir;
    }

    /**
     * Returns specified application cache directory. Cache directory will be
     * created on SD card by defined path if card is mounted and app has
     * appropriate permission. Else - Android defines cache directory on
     * device's file system.
     *
     * @param cacheDir Cache directory path (e.g.: "AppCacheDir",
     *                 "AppDir/cache/images")
     * @return Cache {@link File directory}
     */
    public static File getOwnCacheDirectory(Context context, String cacheDir) {
        File appCacheDir = null;
        if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) {
            appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);
        }
        if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) {
            appCacheDir = context.getCacheDir();
        }
        return appCacheDir;
    }

    /**
     * Returns specified application cache directory. Cache directory will be
     * created on SD card by defined path if card is mounted and app has
     * appropriate permission. Else - Android defines cache directory on
     * device's file system.
     *
     * @param cacheDir Cache directory path (e.g.: "AppCacheDir",
     *                 "AppDir/cache/images")
     * @return Cache {@link File directory}
     */
    public static File getOwnCacheDirectory(Context context, String cacheDir, boolean preferExternal) {
        assert context != null;
        File appCacheDir = null;
        if (preferExternal && MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                && hasExternalStoragePermission(context)) {
            appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);
        }
        if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) {
            appCacheDir = context.getCacheDir();
        }
        return appCacheDir;
    }

    @SuppressLint("NewApi")
    public static long getDiskSizeRemain(Context context) {
        File path = getIndividualImageCacheDirectory(context);
        StatFs stat = new StatFs(path.getPath());
        long result;
        long block;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
            result = stat.getAvailableBlocksLong();
            block = stat.getBlockSizeLong();
        } else {
            result = stat.getAvailableBlocks();
            block = stat.getBlockSize();
        }
        return result * block;
    }

    private static File getExternalCacheDir(Context context) {
        assert context != null;
        File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
        File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
        if (!appCacheDir.exists()) {
            if (!appCacheDir.mkdirs()) {
                return null;
            }
            try {
                new File(appCacheDir, ".nomedia").createNewFile();
            } catch (IOException e) {
            }
        }
        return appCacheDir;
    }

    private static boolean hasExternalStoragePermission(Context context) {
        assert context != null;
        int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
        return perm == PackageManager.PERMISSION_GRANTED;
    }

    public static void closeIO(Closeable... io) {
        for (Closeable close : io) {
            if (close != null) {
                try {
                    close.close();
                    close = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static String getMimeType(String path) {
        String defaultContentType = "application/octet-stream";
        if (TextUtils.isEmpty(path))
            return defaultContentType;
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = null;
        try {
            contentTypeFor = fileNameMap.getContentTypeFor(path);
        } catch (Throwable t) {
            PrintStream stream = null;
//            try {
//                stream = LogUtils.getErrorPrintStream();
//                if (stream != null) {
//                    t.printStackTrace(stream);
//                }
//                contentTypeFor = getMimeType1(path);
//            } catch (Throwable e) {
//                e.printStackTrace();
//            } finally {
//                closeIO(stream);
//            }
        }
        return TextUtils.isEmpty(contentTypeFor) ? defaultContentType : contentTypeFor;
    }

    /**
     * @return The MIME type for the given file.
     */
    public static String getMimeType(File file) {
        String extension = getFileExtension(file.getName());
        return getMimeTypeByExtension(extension);
//        if (extension.length() > 0)
//            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
//        return "application/octet-stream";
    }

    /**
     * @return The MIME type for the given file.
     */
    public static String getMimeType1(String path) {
        String extension = getFileExtension(path);
        return getMimeTypeByExtension(extension);
//        if (extension.length() > 0)
//            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
//        return "application/octet-stream";
    }

    /**
     * @return The MIME type for the given file.
     */
    private static String getMimeTypeByExtension(String extension) {
        if (extension.length() > 0)
            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
        return "application/octet-stream";
    }

    public static String getDefaultSavePath() {
        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "oMiniBox");
        if (!file.exists()) {
            file.mkdirs();
        }
        return file.getAbsolutePath();
    }

    /* Checks if external storage is available for read and write */
    public static boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    /* Checks if external storage is available to at least read */
    public static boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    public static long getFolderSize(File file) {
        long size = 0;
        File[] fileList = file.listFiles();
        for (int i = 0; i < fileList.length; i++) {
            if (fileList[i].isDirectory()) {
                size = size + getFolderSize(fileList[i]);
            } else {
                size = size + fileList[i].length();
            }
        }
        return size;
    }


    public static void deleteFolderFile(String filePath, boolean deleteThisPath)
            throws IOException {
        if (!TextUtils.isEmpty(filePath)) {
            File file = new File(filePath);

            if (file.isDirectory()) {// 处理目录
                File files[] = file.listFiles();
                for (int i = 0; i < files.length; i++) {
                    deleteFolderFile(files[i].getAbsolutePath(), true);
                }
            }
            if (deleteThisPath) {
                if (!file.isDirectory()) {// 如果是文件,删除
                    file.delete();
                } else {// 目录
                    if (file.listFiles().length == 0) {// 目录下没有文件或者目录,删除
                        file.delete();
                    }
                }
            }
        }
    }

}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/LogUtils.java
================================================
/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package record.wilson.flutter.com.flutter_plugin_record.utils;

import android.content.Context;
import android.util.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;

public class LogUtils {
    public static String LOG_PREFIX         = "";
    private static int    LOG_PREFIX_LENGTH  = LOG_PREFIX.length();
    private static int    MAX_LOG_TAG_LENGTH = 23;

    public static boolean LOGGING_ENABLED = true;

    public static String makeLogTag(String str) {
        if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
            return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
        }
        return LOG_PREFIX + str;
    }

    /**
     * Don't use this when obfuscating class names!
     */
    public static String makeLogTag(Class cls) {
        return makeLogTag(cls.getSimpleName());
    }

    public static String getTAG() {
        return LOG_PREFIX;
    }

    public static void LOGD(String message) {
        if (LOGGING_ENABLED) {
            Log.d(getTag(), message != null ? message : "message is null");
        }
    }

    public static void LOGD(final String tag, String message) {
        if (LOGGING_ENABLED) {
            if (Log.isLoggable(tag, Log.DEBUG)) {
                Log.d(tag, message);
            }
        }
    }

    public static void LOGDPrintProcess(final String tag, String message) {
        if (LOGGING_ENABLED) {
            if (Log.isLoggable(tag, Log.DEBUG)) {
                String msg = getLogMessage(message);
                Log.d(tag, msg);
            }
        }
    }

    public static void LOGD(final String tag, String message, Throwable cause) {
        if (LOGGING_ENABLED) {
            if (Log.isLoggable(tag, Log.DEBUG)) {
                Log.d(tag, message, cause);
            }
        }
    }

    public static void LOGV(String message) {
        if (LOGGING_ENABLED) {
            Log.v(getTag(), message != null ? message : "message is null");
        }
    }

    public static void LOGV(final String tag, String message) {
        if (LOGGING_ENABLED) {
            if (Log.isLoggable(tag, Log.VERBOSE)) {
                Log.v(tag, message);
            }
        }
    }

    public static void LOGV(final String tag, String message, Throwable cause) {
        if (LOGGING_ENABLED) {
            if (Log.isLoggable(tag, Log.VERBOSE)) {
                Log.v(tag, message, cause);
            }
        }
    }

    public static void LOGI(String message) {
        if (LOGGING_ENABLED) {
            Log.i(getTag(), message != null ? message : "message is null");
        }
    }

    public static void LOGI(final String tag, String message) {
        if (LOGGING_ENABLED) {
            Log.i(tag, message);
        }
    }

    public static void LOGI(final String tag, String message, Throwable cause) {
        if (LOGGING_ENABLED) {
            Log.i(tag, message, cause);
        }
    }

    public static void LOGW(String message) {
        if (LOGGING_ENABLED) {
            Log.w(getTag(), message != null ? message : "message is null");
        }
    }

    public static void LOGW(final String tag, String message) {
        if (LOGGING_ENABLED) {
            Log.w(tag, message);
        }
    }

    public static void LOGW(final String tag, String message, Throwable cause) {
        if (LOGGING_ENABLED) {
            Log.w(tag, message, cause);
        }
    }

    public static void LOGE(String message) {
        if (LOGGING_ENABLED) {
            Log.e(getTag(), message != null ? message : "message is null");
        }
    }

    public static void LOGE(final String tag, String message) {
        if (LOGGING_ENABLED) {
            Log.e(tag, message);
        }
    }

    private static final int MAX_CHAR_ONE_LINE = 82;

    public static void LOGEPrintProcess(final String tag, String message) {
        if (LOGGING_ENABLED) {
            String msg = getLogMessage(message);
            Log.e(tag, msg);
        }
    }

    private static final int LOG_LENGTH = 100;

    private static String getLogMessage(String msg) {
        StringBuilder sb = new StringBuilder();
        String[] split = msg.split("\n");
        addPrintHead(sb);
        sb.append("\n")
                .append(getFullFillTopLine(new Date().toString(), LOG_PREFIX))
                .append("\n");
        for (int i = 0; i < split.length; i++) {
            sb.append(getLine(split[i])).append("\n");
        }
        sb.append(getFullFillBottomLine("pid : " + android.os.Process.myPid(), "tid : " + Thread.currentThread().getId())).append("\n");
        addPrintBottom(sb);
        return sb.toString();
    }

    private static void addPrintBottom(StringBuilder sb) {
        sb.append("└");
        fillLine(sb, LOG_LENGTH, "-");
        sb.append("┘");
    }

    private static final int PADDING_LEFT = 4;

    private static String getLine(String s) {
        int remind = LOG_LENGTH - 4 - s.getBytes().length - PADDING_LEFT;
        StringBuilder temp = new StringBuilder();
        temp.append("├┤");
        for (int j = 0; j < PADDING_LEFT; j++) {
            temp.append(" ");
        }
        temp.append(s);
        fillLine(temp, remind, " ");
        temp.append("├┤");
        return temp.toString();
    }

    private static final int POSITION_TOP    = 0;
    private static final int POSITION_MIDDLE = 1;
    private static final int POSITION_BOTTOM = 2;

    private static String getFullFillTopLine(String... s) {
        return getFullFillLine(POSITION_TOP, s);
    }

    private static String getFullFillBottomLine(String... s) {
        return getFullFillLine(POSITION_BOTTOM, s);
    }

    private static String getFullFillLine(int position, String... s) {
        int length = 0;
        for (int i = 0; i < s.length; i++) {
            int l = s[i].getBytes().length;
            length += l;
        }
        int remind = LOG_LENGTH - length - 4;
        int each = remind / (s.length + 1);
        int fix = each * (s.length + 1) - remind;
        StringBuilder temp = new StringBuilder();
        switch (position) {
            case POSITION_TOP:
                temp.append("├┬");
                break;
            case POSITION_MIDDLE:
                temp.append("├┤");
                break;
            case POSITION_BOTTOM:
                temp.append("├┴");
                break;
        }
        for (int j = 0; j < s.length; j++) {
            fillLine(temp, each, "-");
            temp.append(s[j]);
        }
        fillLine(temp, each - fix, "-");
        switch (position) {
            case POSITION_TOP:
                temp.append("┬┤");
                break;
            case POSITION_MIDDLE:
                temp.append("├┤");
                break;
            case POSITION_BOTTOM:
                temp.append("┴┤");
                break;
        }
        return temp.toString();
    }

    private static void fillLine(StringBuilder sb, int length, String filler) {
        int fill = length / filler.length();
        for (int k = 0; k < fill; k++) {
            sb.append(filler);
        }
    }

    private static void addPrintHead(StringBuilder sb) {
        sb.append("┌");
        fillLine(sb, LOG_LENGTH, "-");
        sb.append("┐");
    }


    public static void LOGE(final String tag, String message, Throwable cause) {
        if (LOGGING_ENABLED) {
            Log.e(tag, message, cause);
        }
    }

    public static void write2Log(Context context, String msg) {
        write2Log(context, msg, new StringBuilder(new Date().toString()).append("_log").toString());
    }

    public static PrintStream getErrorPrintStream(Context context) throws FileNotFoundException {
        File logFile = new File(FileTool.getIndividualLogCacheDirectory(context), new StringBuilder(new Date().toString()).append("_log").toString() + ".txt");
        return new PrintStream(new FileOutputStream(logFile));
    }

    public static void write2Log(Context context, String msg, String name) {
        if (LOGGING_ENABLED) {
            File logFile = new File(FileTool.getCacheDirectory(context), name + ".txt");
            BufferedWriter writer = null;
            try {
                if (!logFile.exists()) logFile.createNewFile();
                writer = new BufferedWriter(new FileWriter(logFile, true));
                writer.newLine();
                writer.append(msg);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                FileTool.closeIO(writer);
            }
        }
    }


    private static String getTag() {
        StackTraceElement[] trace = new Throwable().fillInStackTrace()
                .getStackTrace();
        String callingClass = "";
        for (int i = 2; i < trace.length; i++) {
            Class<?> clazz = trace[i].getClass();
            if (!clazz.equals(Log.class)) {
                callingClass = trace[i].getClassName();
                callingClass = callingClass.substring(callingClass
                        .lastIndexOf('.') + 1);
                break;
            }
        }
        return callingClass;
    }

    private LogUtils() {
    }

    public static IntervalCounter getIntervalCounter() {
        return new IntervalCounter();
    }

    public static class IntervalCounter {
        private long timeStamps;

        private IntervalCounter() {
            timeStamps = System.currentTimeMillis();
        }

        public long getInterval() {
            long result = System.currentTimeMillis() - timeStamps;
            timeStamps = System.currentTimeMillis();
            return result;
        }

        public long getTimeStamps() {
            timeStamps = System.currentTimeMillis();
            return timeStamps;
        }

        public String getIntervalStr() {
            return " interval is " + getInterval();
        }
    }

    public static String getCurCpuFreq() {
        String result = "N/A";
        try {
            FileReader fr = new FileReader(
                    "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
            BufferedReader br = new BufferedReader(fr);
            String text = br.readLine();
            result = text.trim();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayState.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

public enum PlayState {
    prepare, start, pause, complete
}


================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayUtilsPlus.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

import android.media.MediaPlayer;


public class PlayUtilsPlus {
    PlayStateChangeListener playStateChangeListener;
    MediaPlayer player;

    public PlayUtilsPlus() {
    }

    public void setPlayStateChangeListener(PlayStateChangeListener listener) {
        this.playStateChangeListener = listener;
        //  this.playStateChangeListener.onPlayStateChange(PlayState.prepare);
    }

    public void startPlaying(String filePath) {
        try {
            isPause=false;
            this.player = new MediaPlayer();
            this.player.setDataSource(filePath);
            this.player.prepareAsync();
            this.player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                public void onPrepared(MediaPlayer mp) {
                    PlayUtilsPlus.this.player.start();
                }
            });
            if (this.playStateChangeListener != null) {
                // this.playStateChangeListener.onPlayStateChange(PlayState.start);
            }

            this.player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                public void onCompletion(MediaPlayer mp) {
                    PlayUtilsPlus.this.stopPlaying();
                }
            });
        } catch (Exception var3) {
            var3.printStackTrace();
        }

    }
    Boolean isPause = false;

    public boolean pausePlay() {
        try {
            if (this.player.isPlaying() && !isPause) {
                this.player.pause();
                isPause = true;
            } else {
                this.player.start();
                isPause = false;
            }

        } catch (Exception var2) {
            var2.printStackTrace();
        }
        return isPause ;
    }

    public void stopPlaying() {
        try {
            if (this.player != null) {
                this.player.stop();
                this.player.reset();
                this.player=null;
                if (this.playStateChangeListener != null) {
                    this.playStateChangeListener.onPlayStateChange(PlayState.complete);
                }
            }
        } catch (Exception var2) {
            var2.printStackTrace();
        }

    }

    public boolean isPlaying() {
        try {
            return this.player != null && this.player.isPlaying();
        } catch (Exception var2) {
            return false;
        }
    }

    public interface PlayStateChangeListener {
        void onPlayStateChange(PlayState playState);
    }
}




================================================
FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/RecorderUtil.java
================================================
package record.wilson.flutter.com.flutter_plugin_record.utils;

import android.os.Environment;
import android.util.Log;

import com.maple.recorder.recording.AudioChunk;
import com.maple.recorder.recording.AudioRecordConfig;
import com.maple.recorder.recording.MsRecorder;
import com.maple.recorder.recording.PullTransport;
import com.maple.recorder.recording.Recorder;

import java.io.File;
import java.util.Date;


public class RecorderUtil {

    Recorder recorder;
    public static String rootPath = "/yun_ke_fu/flutter/wav_file/";
    String voicePath;
    PlayUtilsPlus playUtils;

    RecordListener recordListener;
    PlayStateListener playStateListener;

    public RecorderUtil() {
        initVoice();
    }



    public RecorderUtil(String path) {
        voicePath =path;
    }





    public void addPlayAmplitudeListener(RecordListener recordListener) {
        this.recordListener = recordListener;
    }
    public void addPlayStateListener(PlayStateListener playStateListener) {
        this.playStateListener = playStateListener;
    }

    private void initVoice() {
        initPath();
        initVoicePath();
        initRecorder();
    }



    //初始化存储路径
    private void initPath() {
        String ROOT = "";// /storage/emulated/0
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            ROOT = Environment.getExternalStorageDirectory().getPath();
            Log.e("voice", "系统方法:" + ROOT);
        }
        rootPath = ROOT + rootPath;

        File lrcFile = new File(rootPath);
        if (!lrcFile.exists()) {
            lrcFile.mkdirs();
        }

        Log.e("voice", "初始存储路径" + rootPath);
    }


    private void initVoicePath() {
        String forDate = DateUtils.dateToString(new Date());
        String name = "wav-" + forDate;
        voicePath = rootPath + name + ".wav";
        Log.e("voice", "初始化语音路径" + voicePath);


    }


    private void initRecorder() {
        recorder = MsRecorder.wav(
                new File(voicePath),
                new AudioRecordConfig.Default(),
                new PullTransport.Default()
                        .setOnAudioChunkPulledListener(new PullTransport.OnAudioChunkPulledListener() {
                            @Override
                            public void onAudioChunkPulled(AudioChunk audioChunk) {
                                if (recordListener != null) {
                                    recordListener.onPlayAmplitude(audioChunk.maxAmplitude());
                                }
                            }
                        })

        );
    }

    public void startRecord() {
        if (recordListener != null) {
            recordListener.onVoicePathSuccess(voicePath);
        }

        recorder.stopRecording();
        recorder.startRecording();
    }

    public void stopRecord() {
        recorder.stopRecording();
    }

    public void playVoice() {
        if (playUtils == null) {
            playUtils = new PlayUtilsPlus();
            playUtils.setPlayStateChangeListener(new PlayUtilsPlus.PlayStateChangeListener() {
                @Override
                public void onPlayStateChange(PlayState playState) {
                    playStateListener.playState(playState);
                }
            });
        }
        if(playUtils.isPlaying()) {
            playUtils.stopPlaying();
        }
        playUtils.startPlaying(voicePath);
    }

    public  boolean pausePlay(){
        LogUtils.LOGD("wilson","pausePlay");
        boolean isPlaying = playUtils.pausePlay();
        return isPlaying;
    }

    public  void stopPlay(){
        LogUtils.LOGD("wilson","stopPlay");
        playUtils.stopPlaying();
    }

    public interface RecordListener {
        void onPlayAmplitude(Double amplitude);

        void onVoicePathSuccess(String voicePath);

    }

    public interface PlayStateListener {

        void playState(PlayState playState);

    }
}


================================================
FILE: example/.flutter-plugins-dependencies
================================================
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_plugin_record","path":"/Users/wilson/aochuang/FlutterDemo/flutter_plugin_record/","dependencies":[]},{"name":"path_provider","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.24/","dependencies":[]},{"name":"shared_preferences","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.12+4/","dependencies":[]}],"android":[{"name":"flutter_plugin_record","path":"/Users/wilson/aochuang/FlutterDemo/flutter_plugin_record/","dependencies":[]},{"name":"path_provider","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.24/","dependencies":[]},{"name":"shared_preferences","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.12+4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4+6/","dependencies":[]},{"name":"shared_preferences_macos","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+11/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_linux-0.0.2+4/","dependencies":["path_provider_linux"]}],"windows":[{"name":"path_provider_windows","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_windows-0.0.1+3/","dependencies":["path_provider_windows"]}],"web":[{"name":"shared_preferences_web","path":"/Users/wilson/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+7/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_plugin_record","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_linux","shared_preferences_macos","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2021-03-24 11:27:57.607425","version":"2.0.3"}

================================================
FILE: example/.gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
/build/

# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java

# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*

# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages


================================================
FILE: example/.metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
  revision: 68587a0916366e9512a78df22c44163d041dd5f3
  channel: stable

project_type: app


================================================
FILE: example/README.md
================================================
# flutter_plugin_record_example


### 使用

### 1, 初始化录制



可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化

    //实例化对象 
    FlutterPluginRecord   recordPlugin = new FlutterPluginRecord();
    //    初始化
    recordPlugin.init()

### 2, 开始录制
   
     recordPlugin.start()
### 3, 停止录制
     recordPlugin.stop()
### 4, 播放 暂停,停止播放
#### 1,播放
     
     recordPlugin.play()
     
#### 2, 暂停和继续播放
       
     recordPlugin.pausePlay();

#### 3, 停止播放
    
     recordPlugin.stopPlay();
      
### 3, 释放资源
可以在页面退出的时候进行资源释放 比如在  dispose方法中调用如下代码

     recordPlugin.dispose()
     
### 4,回调监听  
1,初始化回调监听  

  
    ///初始化方法的监听
    recordPlugin.responseFromInit.listen((data) {
      if (data) {
        print("初始化成功");
      } else {
        print("初始化失败");
      }
    });
    

2,开始录制停止录制监听

     /// 开始录制或结束录制的监听
        recordPlugin.response.listen((data) {
          if (data.msg == "onStop") {
            ///结束录制时会返回录制文件的地址方便上传服务器
            print("onStop  " + data.path);
          } else if (data.msg == "onStart") {
            print("onStart --");
          }
        });
    
3,录制声音大小回调监听


     ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
        recordPlugin.responseFromAmplitude.listen((data) {
          var voiceData = double.parse(data.msg);
          var tempVoice = "";
          if (voiceData > 0 && voiceData < 0.1) {
            tempVoice = "images/voice_volume_2.png";
          } else if (voiceData > 0.2 && voiceData < 0.3) {
            tempVoice = "images/voice_volume_3.png";
          } else if (voiceData > 0.3 && voiceData < 0.4) {
            tempVoice = "images/voice_volume_4.png";
          } else if (voiceData > 0.4 && voiceData < 0.5) {
            tempVoice = "images/voice_volume_5.png";
          } else if (voiceData > 0.5 && voiceData < 0.6) {
            tempVoice = "images/voice_volume_6.png";
          } else if (voiceData > 0.6 && voiceData < 0.7) {
            tempVoice = "images/voice_volume_7.png";
          } else if (voiceData > 0.7 && voiceData < 1) {
            tempVoice = "images/voice_volume_7.png";
          }
          setState(() {
            voiceIco = tempVoice;
            if(overlayEntry!=null){
              overlayEntry.markNeedsBuild();
            }
          });
    
          print("振幅大小   " + voiceData.toString() + "  " + voiceIco);
        });
    
     
## 2,录制组件的使用


### 1,在使用的页面进行导入package

    import 'package:flutter_plugin_record/index.dart';  
        
    
    

    
### 2,在使用的地方引入VoiceWidget组件
    
    new VoiceWidget(),
    
    
    
## TODO

* [x] 实现发送语音时间按下抬起时间很短提示
* [x] 优化代码
* [x] 实现录制完成文件路径回调功能,方面使用者可以把录音文件上传服务器


## 关注公众号获取更多内容

  ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190926100941125.jpg)



================================================
FILE: example/android/.gitignore
================================================
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties


================================================
FILE: example/android/app/build.gradle
================================================
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    compileSdkVersion 29

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "record.wilson.flutter.com.flutter_plugin_record_example"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}


================================================
FILE: example/android/app/src/debug/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="record.wilson.flutter.com.flutter_plugin_record_example">
    <!-- Flutter needs it to communicate with the running application
         to allow setting breakpoints, to provide hot reload, etc.
    -->
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>


================================================
FILE: example/android/app/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="record.wilson.flutter.com.flutter_plugin_record_example"
    >

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <application
        android:requestLegacyExternalStorage="true"
        tools:replace="android:label"
        android:icon="@mipmap/ic_launcher"
        android:label="flutter_plugin_record_example"
        android:networkSecurityConfig="@xml/network_security_config">
        <meta-data
                android:name="flutterEmbedding"
                android:value="2" />
        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <meta-data
                    android:name="io.flutter.embedding.android.SplashScreenDrawable"
                    android:resource="@drawable/launch_background" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>


================================================
FILE: example/android/app/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record_example/MainActivity.kt
================================================
package record.wilson.flutter.com.flutter_plugin_record_example

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity() {

}


================================================
FILE: example/android/app/src/main/res/drawable/launch_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@android:color/white" />

    <!-- You can insert your own image assets here -->
    <!-- <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/launch_image" />
    </item> -->
</layer-list>


================================================
FILE: example/android/app/src/main/res/values/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
        <!-- Show a splash screen on the activity. Automatically removed when
             Flutter draws its first frame -->
        <item name="android:windowBackground">@drawable/launch_background</item>
    </style>
</resources>


================================================
FILE: example/android/app/src/main/res/xml/network_security_config.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>


================================================
FILE: example/android/app/src/profile/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="record.wilson.flutter.com.flutter_plugin_record_example">
    <!-- Flutter needs it to communicate with the running application
         to allow setting breakpoints, to provide hot reload, etc.
    -->
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>


================================================
FILE: example/android/build.gradle
================================================
buildscript {
//    ext.kotlin_version = '1.2.71'
    ext.kotlin_version = '1.3.50'

    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: example/android/gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Dec 18 09:02:45 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip


================================================
FILE: example/android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M

android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true


================================================
FILE: example/android/res/values/strings_en.arb
================================================
{}

================================================
FILE: example/android/settings.gradle
================================================
include ':app'

def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}


================================================
FILE: example/ios/Flutter/.last_build_id
================================================
dcb78416cfbfe60288e8c774d67c139c

================================================
FILE: example/ios/Flutter/AppFrameworkInfo.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key>
  <string>$(DEVELOPMENT_LANGUAGE)</string>
  <key>CFBundleExecutable</key>
  <string>App</string>
  <key>CFBundleIdentifier</key>
  <string>io.flutter.flutter.app</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>App</string>
  <key>CFBundlePackageType</key>
  <string>FMWK</string>
  <key>CFBundleShortVersionString</key>
  <string>1.0</string>
  <key>CFBundleSignature</key>
  <string>????</string>
  <key>CFBundleVersion</key>
  <string>1.0</string>
  <key>MinimumOSVersion</key>
  <string>8.0</string>
</dict>
</plist>


================================================
FILE: example/ios/Flutter/Debug.xcconfig
================================================
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"


================================================
FILE: example/ios/Flutter/Flutter.podspec
================================================
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#       This is a generated file; do not edit or check into version control.
#

Pod::Spec.new do |s|
  s.name             = 'Flutter'
  s.version          = '1.0.0'
  s.summary          = 'High-performance, high-fidelity mobile apps.'
  s.homepage         = 'https://flutter.io'
  s.license          = { :type => 'MIT' }
  s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
  s.source           = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
  s.ios.deployment_target = '8.0'
  # Framework linking is handled by Flutter tooling, not CocoaPods.
  # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
  s.vendored_frameworks = 'path/to/nothing'
end


================================================
FILE: example/ios/Flutter/Release.xcconfig
================================================
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"


================================================
FILE: example/ios/Podfile
================================================
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
  end
end


================================================
FILE: example/ios/Runner/AppDelegate.swift
================================================
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}


================================================
FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "size" : "20x20",
      "idiom" : "iphone",
      "filename" : "Icon-App-20x20@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "20x20",
      "idiom" : "iphone",
      "filename" : "Icon-App-20x20@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-App-29x29@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-App-29x29@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-App-29x29@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "40x40",
      "idiom" : "iphone",
      "filename" : "Icon-App-40x40@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "40x40",
      "idiom" : "iphone",
      "filename" : "Icon-App-40x40@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "60x60",
      "idiom" : "iphone",
      "filename" : "Icon-App-60x60@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "60x60",
      "idiom" : "iphone",
      "filename" : "Icon-App-60x60@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "20x20",
      "idiom" : "ipad",
      "filename" : "Icon-App-20x20@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "20x20",
      "idiom" : "ipad",
      "filename" : "Icon-App-20x20@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "29x29",
      "idiom" : "ipad",
      "filename" : "Icon-App-29x29@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "29x29",
      "idiom" : "ipad",
      "filename" : "Icon-App-29x29@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "40x40",
      "idiom" : "ipad",
      "filename" : "Icon-App-40x40@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "40x40",
      "idiom" : "ipad",
      "filename" : "Icon-App-40x40@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "76x76",
      "idiom" : "ipad",
      "filename" : "Icon-App-76x76@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "76x76",
      "idiom" : "ipad",
      "filename" : "Icon-App-76x76@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "83.5x83.5",
      "idiom" : "ipad",
      "filename" : "Icon-App-83.5x83.5@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "1024x1024",
      "idiom" : "ios-marketing",
      "filename" : "Icon-App-1024x1024@1x.png",
      "scale" : "1x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}


================================================
FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "filename" : "LaunchImage.png",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "filename" : "LaunchImage@2x.png",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "filename" : "LaunchImage@3x.png",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}


================================================
FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# Launch Screen Assets

You can customize the launch screen with your own desired assets by replacing the image files in this directory.

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.

================================================
FILE: example/ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
                            </imageView>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
                        </constraints>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
    <resources>
        <image name="LaunchImage" width="168" height="185"/>
    </resources>
</document>


================================================
FILE: example/ios/Runner/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
    </dependencies>
    <scenes>
        <!--Flutter View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
        </scene>
    </scenes>
</document>


================================================
FILE: example/ios/Runner/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>flutter_plugin_record_example</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(FLUTTER_BUILD_NAME)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSMicrophoneUsageDescription</key>
	<string>打开话筒</string>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
</dict>
</plist>


================================================
FILE: example/ios/Runner/Runner-Bridging-Header.h
================================================
#import "GeneratedPluginRegistrant.h"

================================================
FILE: example/ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
		481940F04663AD0B6D9C404B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6140FFF951F9E4BD7AB5108D /* Pods_Runner.framework */; };
		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
			isa = PBXCopyFilesBuildPhase;
			buildActionMask = 2147483647;
			dstPath = "";
			dstSubfolderSpec = 10;
			files = (
			);
			name = "Embed Frameworks";
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
		1935C927EF54753EFE93F347 /* 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 = "<group>"; };
		26B885D5752B3F53CA047B5E /* 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 = "<group>"; };
		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
		6140FFF951F9E4BD7AB5108D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		F345A59A40866AC079FB2659 /* 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 = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		97C146EB1CF9000F007C117D /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				481940F04663AD0B6D9C404B /* Pods_Runner.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		61F2D205163EA3AEA9F3A435 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				6140FFF951F9E4BD7AB5108D /* Pods_Runner.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		9475E7547E3F8A95EB266079 /* Pods */ = {
			isa = PBXGroup;
			children = (
				26B885D5752B3F53CA047B5E /* Pods-Runner.debug.xcconfig */,
				1935C927EF54753EFE93F347 /* Pods-Runner.release.xcconfig */,
				F345A59A40866AC079FB2659 /* Pods-Runner.profile.xcconfig */,
			);
			name = Pods;
			path = Pods;
			sourceTree = "<group>";
		};
		9740EEB11CF90186004384FC /* Flutter */ = {
			isa = PBXGroup;
			children = (
				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
				9740EEB21CF90195004384FC /* Debug.xcconfig */,
				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
				9740EEB31CF90195004384FC /* Generated.xcconfig */,
			);
			name = Flutter;
			sourceTree = "<group>";
		};
		97C146E51CF9000F007C117D = {
			isa = PBXGroup;
			children = (
				9740EEB11CF90186004384FC /* Flutter */,
				97C146F01CF9000F007C117D /* Runner */,
				97C146EF1CF9000F007C117D /* Products */,
				9475E7547E3F8A95EB266079 /* Pods */,
				61F2D205163EA3AEA9F3A435 /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		97C146EF1CF9000F007C117D /* Products */ = {
			isa = PBXGroup;
			children = (
				97C146EE1CF9000F007C117D /* Runner.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		97C146F01CF9000F007C117D /* Runner */ = {
			isa = PBXGroup;
			children = (
				97C146FA1CF9000F007C117D /* Main.storyboard */,
				97C146FD1CF9000F007C117D /* Assets.xcassets */,
				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
				97C147021CF9000F007C117D /* Info.plist */,
				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
			);
			path = Runner;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		97C146ED1CF9000F007C117D /* Runner */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
			buildPhases = (
				504AAE8408988510F90F1B85 /* [CP] Check Pods Manifest.lock */,
				9740EEB61CF901F6004384FC /* Run Script */,
				97C146EA1CF9000F007C117D /* Sources */,
				97C146EB1CF9000F007C117D /* Frameworks */,
				97C146EC1CF9000F007C117D /* Resources */,
				9705A1C41CF9048500538489 /* Embed Frameworks */,
				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
				A16AE44BD745529DCD8AAD30 /* [CP] Embed Pods Frameworks */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = Runner;
			productName = Runner;
			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		97C146E61CF9000F007C117D /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastUpgradeCheck = 1020;
				ORGANIZATIONNAME = "";
				TargetAttributes = {
					97C146ED1CF9000F007C117D = {
						CreatedOnToolsVersion = 7.3.1;
						LastSwiftMigration = 1100;
					};
				};
			};
			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
			compatibilityVersion = "Xcode 9.3";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 97C146E51CF9000F007C117D;
			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				97C146ED1CF9000F007C117D /* Runner */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		97C146EC1CF9000F007C117D /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputPaths = (
			);
			name = "Thin Binary";
			outputPaths = (
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
		};
		504AAE8408988510F90F1B85 /* [CP] Check Pods Manifest.lock */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputFileListPaths = (
			);
			inputPaths = (
				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
				"${PODS_ROOT}/Manifest.lock",
			);
			name = "[CP] Check Pods Manifest.lock";
			outputFileListPaths = (
			);
			outputPaths = (
				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			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";
			showEnvVarsInLog = 0;
		};
		9740EEB61CF901F6004384FC /* Run Script */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputPaths = (
			);
			name = "Run Script";
			outputPaths = (
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
		};
		A16AE44BD745529DCD8AAD30 /* [CP] Embed Pods Frameworks */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputFileListPaths = (
				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
			);
			name = "[CP] Embed Pods Frameworks";
			outputFileListPaths = (
				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
			showEnvVarsInLog = 0;
		};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		97C146EA1CF9000F007C117D /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				97C146FB1CF9000F007C117D /* Base */,
			);
			name = Main.storyboard;
			sourceTree = "<group>";
		};
		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				97C147001CF9000F007C117D /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		249021D3217E4FDB00AE95B9 /* Profile */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SUPPORTED_PLATFORMS = iphoneos;
				TARGETED_DEVICE_FAMILY = "1,2";
				VALIDATE_PRODUCT = YES;
			};
			name = Profile;
		};
		249021D4217E4FDB00AE95B9 /* Profile */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
				ENABLE_BITCODE = NO;
				FRAMEWORK_SEARCH_PATHS = (
					"$(inherited)",
					"$(PROJECT_DIR)/Flutter",
				);
				INFOPLIST_FILE = Runner/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				LIBRARY_SEARCH_PATHS = (
					"$(inherited)",
					"$(PROJECT_DIR)/Flutter",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
				SWIFT_VERSION = 5.0;
				VERSIONING_SYSTEM = "apple-generic";
			};
			name = Profile;
		};
		97C147031CF9000F007C117D /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		97C147041CF9000F007C117D /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SUPPORTED_PLATFORMS = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
				TARGETED_DEVICE_FAMILY = "1,2";
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		97C147061CF9000F007C117D /* Debug */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
				ENABLE_BITCODE = NO;
				FRAMEWORK_SEARCH_PATHS = (
					"$(inherited)",
					"$(PROJECT_DIR)/Flutter",
				);
				INFOPLIST_FILE = Runner/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				LIBRARY_SEARCH_PATHS = (
					"$(inherited)",
					"$(PROJECT_DIR)/Flutter",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 5.0;
				VERSIONING_SYSTEM = "apple-generic";
			};
			name = Debug;
		};
		97C147071CF9000F007C117D /* Release */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
				ENABLE_BITCODE = NO;
				FRAMEWORK_SEARCH_PATHS = (
					"$(inherited)",
					"$(PROJECT_DIR)/Flutter",
				);
				INFOPLIST_FILE = Runner/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				LIBRARY_SEARCH_PATHS = (
					"$(inherited)",
					"$(PROJECT_DIR)/Flutter",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
				SWIFT_VERSION = 5.0;
				VERSIONING_SYSTEM = "apple-generic";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				97C147031CF9000F007C117D /* Debug */,
				97C147041CF9000F007C117D /* Release */,
				249021D3217E4FDB00AE95B9 /* Profile */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				97C147061CF9000F007C117D /* Debug */,
				97C147071CF9000F007C117D /* Release */,
				249021D4217E4FDB00AE95B9 /* Profile */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = 97C146E61CF9000F007C117D /* Project object */;
}


================================================
FILE: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:Runner.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>PreviewsEnabled</key>
	<false/>
</dict>
</plist>


================================================
FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1020"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
               BuildableName = "Runner.app"
               BlueprintName = "Runner"
               ReferencedContainer = "container:Runner.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
            BuildableName = "Runner.app"
            BlueprintName = "Runner"
            ReferencedContainer = "container:Runner.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <AdditionalOptions>
      </AdditionalOptions>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
            BuildableName = "Runner.app"
            BlueprintName = "Runner"
            ReferencedContainer = "container:Runner.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Profile"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
            BuildableName = "Runner.app"
            BlueprintName = "Runner"
            ReferencedContainer = "container:Runner.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:Runner.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:Pods/Pods.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>PreviewsEnabled</key>
	<false/>
</dict>
</plist>


================================================
FILE: example/lib/generated/i18n.dart
================================================
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// ignore_for_file: non_constant_identifier_names
// ignore_for_file: camel_case_types
// ignore_for_file: prefer_single_quotes

// This file is automatically generated. DO NOT EDIT, all your changes would be lost.
class S implements WidgetsLocalizations {
  const S();

  static S current;

  static const GeneratedLocalizationsDelegate delegate =
    GeneratedLocalizationsDelegate();

  static S of(BuildContext context) => Localizations.of<S>(context, S);

  @override
  TextDirection get textDirection => TextDirection.ltr;

}

class $en extends S {
  const $en();
}

class GeneratedLocalizationsDelegate extends LocalizationsDelegate<S> {
  const GeneratedLocalizationsDelegate();

  List<Locale> get supportedLocales {
    return const <Locale>[
      Locale("en", ""),
    ];
  }

  LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) {
    return (List<Locale> locales, Iterable<Locale> supported) {
      if (locales == null || locales.isEmpty) {
        return fallback ?? supported.first;
      } else {
        return _resolve(locales.first, fallback, supported, withCountry);
      }
    };
  }

  LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) {
    return (Locale locale, Iterable<Locale> supported) {
      return _resolve(locale, fallback, supported, withCountry);
    };
  }

  @override
  Future<S> load(Locale locale) {
    final String lang = getLang(locale);
    if (lang != null) {
      switch (lang) {
        case "en":
          S.current = const $en();
          return SynchronousFuture<S>(S.current);
        default:
          // NO-OP.
      }
    }
    S.current = const S();
    return SynchronousFuture<S>(S.current);
  }

  @override
  bool isSupported(Locale locale) => _isSupported(locale, true);

  @override
  bool shouldReload(GeneratedLocalizationsDelegate old) => false;

  ///
  /// Internal method to resolve a locale from a list of locales.
  ///
  Locale _resolve(Locale locale, Locale fallback, Iterable<Locale> supported, bool withCountry) {
    if (locale == null || !_isSupported(locale, withCountry)) {
      return fallback ?? supported.first;
    }

    final Locale languageLocale = Locale(locale.languageCode, "");
    if (supported.contains(locale)) {
      return locale;
    } else if (supported.contains(languageLocale)) {
      return languageLocale;
    } else {
      final Locale fallbackLocale = fallback ?? supported.first;
      return fallbackLocale;
    }
  }

  ///
  /// Returns true if the specified locale is supported, false otherwise.
  ///
  bool _isSupported(Locale locale, bool withCountry) {
    if (locale != null) {
      for (Locale supportedLocale in supportedLocales) {
        // Language must always match both locales.
        if (supportedLocale.languageCode != locale.languageCode) {
          continue;
        }

        // If country code matches, return this locale.
        if (supportedLocale.countryCode == locale.countryCode) {
          return true;
        }

        // If no country requirement is requested, check if this locale has no country.
        if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) {
          return true;
        }
      }
    }
    return false;
  }
}

String getLang(Locale l) => l == null
  ? null
  : l.countryCode != null && l.countryCode.isEmpty
    ? l.languageCode
    : l.toString();


================================================
FILE: example/lib/main.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_plugin_record_example/path_provider_screen.dart';
import 'package:flutter_plugin_record_example/record_mp3_screen.dart';
import 'package:flutter_plugin_record_example/record_screen.dart';
import 'package:flutter_plugin_record_example/wechat_record_screen.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
      routes: {
        "RecordScreen": (BuildContext context) => new RecordScreen(),
        "RecordMp3Screen": (BuildContext context) => new RecordMp3Screen(),
        "WeChatRecordScreen": (BuildContext context) =>
            new WeChatRecordScreen(),
        "PathProviderScreen": (BuildContext context) =>
            new PathProviderScreen(),
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("flutter版微信语音录制实现"),
      ),
      body: new Center(
        child: new Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            new FlatButton(
                onPressed: () {
                  Navigator.pushNamed<dynamic>(context, "RecordScreen");
                },
                child: new Text("进入语音录制界面")),
            new FlatButton(
                onPressed: () {
                  Navigator.pushNamed<dynamic>(context, "RecordMp3Screen");
                },
                child: new Text("进入录制mp3模式")),
            new FlatButton(
                onPressed: () {
                  Navigator.pushNamed<dynamic>(context, "WeChatRecordScreen");
                },
                child: new Text("进入仿微信录制界面")),
            new FlatButton(
                onPressed: () {
                  Navigator.pushNamed<dynamic>(context, "PathProviderScreen");
                },
                child: new Text("进入文件路径获取界面")),
          ],
        ),
      ),
    );
  }
}


================================================
FILE: example/lib/path_provider_screen.dart
================================================
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

class PathProviderScreen extends StatefulWidget {
  PathProviderScreen({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _PathProviderScreenState createState() => _PathProviderScreenState();
}

class _PathProviderScreenState extends State<PathProviderScreen> {
  Future<Directory> _tempDirectory;
  Future<Directory> _appSupportDirectory;
  Future<Directory> _appLibraryDirectory;
  Future<Directory> _appDocumentsDirectory;
  Future<Directory> _externalDocumentsDirectory;
  Future<List<Directory>> _externalStorageDirectories;
  Future<List<Directory>> _externalCacheDirectories;

  void _requestTempDirectory() {
    setState(() {
      _tempDirectory = getTemporaryDirectory();
    });
  }

  Widget _buildDirectory(
      BuildContext context, AsyncSnapshot<Directory> snapshot) {
    Text text = const Text('');
    if (snapshot.connectionState == ConnectionState.done) {
      if (snapshot.hasError) {
        text = Text('Error: ${snapshot.error}');
      } else if (snapshot.hasData) {
        text = Text('path: ${snapshot.data.path}');
      } else {
        text = const Text('path unavailable');
      }
    }
    return Padding(padding: const EdgeInsets.all(16.0), child: text);
  }

  Widget _buildDirectories(
      BuildContext context, AsyncSnapshot<List<Directory>> snapshot) {
    Text text = const Text('');
    if (snapshot.connectionState == ConnectionState.done) {
      if (snapshot.hasError) {
        text = Text('Error: ${snapshot.error}');
      } else if (snapshot.hasData) {
        final String combined =
        snapshot.data.map((Directory d) => d.path).join(', ');
        text = Text('paths: $combined');
      } else {
        text = const Text('path unavailable');
      }
    }
    return Padding(padding: const EdgeInsets.all(16.0), child: text);
  }

  void _requestAppDocumentsDirectory() {
    setState(() {
      _appDocumentsDirectory = getApplicationDocumentsDirectory();
    });
  }

  void _requestAppSupportDirectory() {
    setState(() {
      _appSupportDirectory = getApplicationSupportDirectory();
    });
  }

  void _requestAppLibraryDirectory() {
    setState(() {
      _appLibraryDirectory = getLibraryDirectory();
    });
  }

  void _requestExternalStorageDirectory() {
    setState(() {
      _externalDocumentsDirectory = getExternalStorageDirectory();
    });
  }

  void _requestExternalStorageDirectories(StorageDirectory type) {
    setState(() {
      _externalStorageDirectories = getExternalStorageDirectories(type: type);
    });
  }

  void _requestExternalCacheDirectories() {
    setState(() {
      _externalCacheDirectories = getExternalCacheDirectories();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("获取文件路径界面"),
      ),
      body: Center(
        child: ListView(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: RaisedButton(
                child: const Text('Get Temporary Directory'),
                onPressed: _requestTempDirectory,
              ),
            ),
            FutureBuilder<Directory>(
                future: _tempDirectory, builder: _buildDirectory),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: RaisedButton(
                child: const Text('Get Application Documents Directory'),
                onPressed: _requestAppDocumentsDirectory,
              ),
            ),
            FutureBuilder<Directory>(
                future: _appDocumentsDirectory, builder: _buildDirectory),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: RaisedButton(
                child: const Text('Get Application Support Directory'),
                onPressed: _requestAppSupportDirectory,
              ),
            ),
            FutureBuilder<Directory>(
                future: _appSupportDirectory, builder: _buildDirectory),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: RaisedButton(
                child: const Text('Get Application Library Directory'),
                onPressed: _requestAppLibraryDirectory,
              ),
            ),
            FutureBuilder<Directory>(
                future: _appLibraryDirectory, builder: _buildDirectory),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: RaisedButton(
                child: Text(
                    '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directory"}'),
                onPressed:
                Platform.isIOS ? null : _requestExternalStorageDirectory,
              ),
            ),
            FutureBuilder<Directory>(
                future: _externalDocumentsDirectory, builder: _buildDirectory),
            Column(children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: RaisedButton(
                  child: Text(
                      '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directories"}'),
                  onPressed: Platform.isIOS
                      ? null
                      : () {
                    _requestExternalStorageDirectories(
                      StorageDirectory.music,
                    );
                  },
                ),
              ),
            ]),
            FutureBuilder<List<Directory>>(
                future: _externalStorageDirectories,
                builder: _buildDirectories),
            Column(children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: RaisedButton(
                  child: Text(
                      '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Cache Directories"}'),
                  onPressed:
                  Platform.isIOS ? null : _requestExternalCacheDirectories,
                ),
              ),
            ]),
            FutureBuilder<List<Directory>>(
                future: _externalCacheDirectories, builder: _buildDirectories),
          ],
        ),
      ),
    );
  }
}

================================================
FILE: example/lib/record_mp3_screen.dart
================================================
import 'dart:io';

import 'package:flustars/flustars.dart';
import 'package:flutter/material.dart';
import 'package:flutter_plugin_record/flutter_plugin_record.dart';
import 'package:path_provider/path_provider.dart';

class RecordMp3Screen extends StatefulWidget {
  @override
  _RecordMp3ScreenState createState() => _RecordMp3ScreenState();
}

class _RecordMp3ScreenState extends State<RecordMp3Screen> {
  FlutterPluginRecord recordPlugin = new FlutterPluginRecord();

  String filePath = "";

  @override
  void initState() {
    super.initState();

    ///初始化方法的监听
    recordPlugin.responseFromInit.listen((data) {
      if (data) {
        print("初始化成功");
      } else {
        print("初始化失败");
      }
    });

    /// 开始录制或结束录制的监听
    recordPlugin.response.listen((data) {
      if (data.msg == "onStop") {
        ///结束录制时会返回录制文件的地址方便上传服务器
        print("onStop  文件路径" + data.path);
        filePath = data.path;
        print("onStop  时长 " + data.audioTimeLength.toString());
      } else if (data.msg == "onStart") {
        print("onStart --");
      } else {
        print("--" + data.msg);
      }
    });

    ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
    recordPlugin.responseFromAmplitude.listen((data) {
      var voiceData = double.parse(data.msg);
      print("振幅大小   " + voiceData.toString());
    });

    recordPlugin.responsePlayStateController.listen((data) {
      print("播放路径   " + data.playPath);
      print("播放状态   " + data.playState);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('录制mp3'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            FlatButton(
              child: Text("初始化录制mp3"),
              onPressed: () {
                _initRecordMp3();
              },
            ),
            FlatButton(
              child: Text("开始录制"),
              onPressed: () {
                start();
              },
            ),
            FlatButton(
              child: Text("根据路径录制mp3文件"),
              onPressed: () {
                _requestAppDocumentsDirectory();
              },
            ),
            FlatButton(
              child: Text("停止录制"),
              onPressed: () {
                stop();
              },
            ),
            FlatButton(
              child: Text("播放"),
              onPressed: () {
                play();
              },
            ),
            FlatButton(
              child: Text("播放本地指定路径录音文件"),
              onPressed: () {
                playByPath(filePath,"file");
              },
            ),
            FlatButton(
              child: Text("播放网络mp3文件"),
              onPressed: () {
                playByPath("https://test-1259809289.cos.ap-nanjing.myqcloud.com/temp.mp3","url");
              },
            ),
            FlatButton(
              child: Text("暂停|继续播放"),
              onPressed: () {
                pause();
              },
            ),
            FlatButton(
              child: Text("停止播放"),
              onPressed: () {
                stopPlay();
              },
            ),
          ],
        ),
      ),
    );
  }

  void _requestAppDocumentsDirectory() {
//    if(Platform.isIOS){
//      //ios相关代码
//      setState(() {
//        getApplicationDocumentsDirectory().then((value) {
//          String nowDataTimeStr = DateUtil.getNowDateMs().toString();
//          String wavPath = value.path + "/" + nowDataTimeStr + ".wav";
//          startByWavPath(wavPath);
//        });
//      });
//    }else if(Platform.isAndroid){
//      //android相关代码
//    }

    setState(() {
      getApplicationDocumentsDirectory().then((value) {
        String nowDataTimeStr = DateUtil.getNowDateMs().toString();
        // TODO  注意IOS 传递的Mp3路径一定是以 .MP3 结尾
        String wavPath ="";
        if (Platform.isIOS) {
           wavPath = value.path + "/" + nowDataTimeStr+".MP3";
        }else{
           wavPath = value.path + "/" + nowDataTimeStr;
        }
        startByWavPath(wavPath);
      });
    });
  }

  ///初始化语音录制的方法
  void _initRecordMp3() async {
    recordPlugin.initRecordMp3();
  }

  ///开始语音录制的方法
  void start() async {
    recordPlugin.start();
  }

  ///根据传递的路径进行语音录制
  void startByWavPath(String wavPath) async {
    recordPlugin.startByWavPath(wavPath);
  }

  ///停止语音录制的方法
  void stop() {
    recordPlugin.stop();
  }

  ///播放语音的方法
  void play() {
    recordPlugin.play();
  }

  ///播放指定路径录音文件  url为iOS播放网络语音,file为播放本地语音文件
  void playByPath(String path,String type) {
    recordPlugin.playByPath(path,type);
  }

  ///暂停|继续播放
  void pause() {
    recordPlugin.pausePlay();
  }

  @override
  void dispose() {
    /// 当界面退出的时候是释放录音资源
    recordPlugin.dispose();
    super.dispose();
  }

  void stopPlay() {
    recordPlugin.stopPlay();
  }
}


================================================
FILE: example/lib/record_screen.dart
================================================
import 'dart:io';

import 'package:flustars/flustars.dart';
import 'package:flutter/material.dart';
import 'package:flutter_plugin_record/flutter_plugin_record.dart';
import 'package:path_provider/path_provider.dart';

class RecordScreen extends StatefulWidget {
  @override
  _RecordScreenState createState() => _RecordScreenState();
}

class _RecordScreenState extends State<RecordScreen> {
  FlutterPluginRecord recordPlugin = new FlutterPluginRecord();

  String filePath = "";

  @override
  void initState() {
    super.initState();

    ///初始化方法的监听
    recordPlugin.responseFromInit.listen((data) {
      if (data) {
        print("初始化成功");
      } else {
        print("初始化失败");
      }
    });

    /// 开始录制或结束录制的监听
    recordPlugin.response.listen((data) {
      if (data.msg == "onStop") {
        ///结束录制时会返回录制文件的地址方便上传服务器
        print("onStop  文件路径" + data.path);
        filePath = data.path;
        print("onStop  时长 " + data.audioTimeLength.toString());
      } else if (data.msg == "onStart") {
        print("onStart --");
      } else {
        print("--" + data.msg);
      }
    });

    ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
    recordPlugin.responseFromAmplitude.listen((data) {
      var voiceData = double.parse(data.msg);
      print("振幅大小   " + voiceData.toString());
    });

    recordPlugin.responsePlayStateController.listen((data) {
      print("播放路径   " + data.playPath);
      print("播放状态   " + data.playState);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('录制wav'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            FlatButton(
              child: Text("初始化"),
              onPressed: () {
                _init();
              },
            ),
            FlatButton(
              child: Text("开始录制"),
              onPressed: () {
                start();
              },
            ),
            FlatButton(
              child: Text("根据路径录制wav文件"),
              onPressed: () {
                _requestAppDocumentsDirectory();
              },
            ),
            FlatButton(
              child: Text("停止录制"),
              onPressed: () {
                stop();
              },
            ),
            FlatButton(
              child: Text("播放"),
              onPressed: () {
                play();
              },
            ),
            FlatButton(
              child: Text("播放本地指定路径录音文件"),
              onPressed: () {
                playByPath(filePath,"file");
              },
            ),
            FlatButton(
              child: Text("播放网络wav文件"),
              onPressed: () {
                playByPath("https://test-1259809289.cos.ap-nanjing.myqcloud.com/test.wav","url");
              },
            ),
            FlatButton(
              child: Text("暂停|继续播放"),
              onPressed: () {
                pause();
              },
            ),
            FlatButton(
              child: Text("停止播放"),
              onPressed: () {
                stopPlay();
              },
            ),
          ],
        ),
      ),
    );
  }

  void _requestAppDocumentsDirectory() {
//    if(Platform.isIOS){
//      //ios相关代码
//      setState(() {
//        getApplicationDocumentsDirectory().then((value) {
//          String nowDataTimeStr = DateUtil.getNowDateMs().toString();
//          String wavPath = value.path + "/" + nowDataTimeStr + ".wav";
//          startByWavPath(wavPath);
//        });
//      });
//    }else if(Platform.isAndroid){
//      //android相关代码
//    }

    setState(() {
      getApplicationDocumentsDirectory().then((value) {
        String nowDataTimeStr = DateUtil.getNowDateMs().toString();
        String wavPath = value.path + "/" + nowDataTimeStr + ".wav";
        print(wavPath);
        startByWavPath(wavPath);
      });
    });
  }

  ///初始化语音录制的方法
  void _init() async {
    recordPlugin.init();
  }

  ///开始语音录制的方法
  void start() async {
    recordPlugin.start();
  }

  ///根据传递的路径进行语音录制
  void startByWavPath(String wavPath) async {
    recordPlugin.startByWavPath(wavPath);
  }

  ///停止语音录制的方法
  void stop() {
    recordPlugin.stop();
  }

  ///播放语音的方法
  void play() {
    recordPlugin.play();
  }

  ///播放指定路径录音文件  url为iOS播放网络语音,file为播放本地语音文件
  void playByPath(String path,String type) {
    recordPlugin.playByPath(path,type);
  }

  ///暂停|继续播放
  void pause() {
    recordPlugin.pausePlay();
  }

  @override
  void dispose() {
    /// 当界面退出的时候是释放录音资源
    recordPlugin.dispose();
    super.dispose();
  }

  void stopPlay() {
    recordPlugin.stopPlay();
  }
}


================================================
FILE: example/lib/wechat_record_screen.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_plugin_record/index.dart';

class WeChatRecordScreen extends StatefulWidget {
  @override
  _WeChatRecordScreenState createState() => _WeChatRecordScreenState();
}

class _WeChatRecordScreenState extends State<WeChatRecordScreen> {
  String toastShow = "悬浮框";
  OverlayEntry overlayEntry;

  showView(BuildContext context) {
    if (overlayEntry == null) {
      overlayEntry = new OverlayEntry(builder: (content) {
        return Positioned(
          top: MediaQuery.of(context).size.height * 0.5 - 80,
          left: MediaQuery.of(context).size.width * 0.5 - 80,
          child: Material(
            child: Center(
              child: Opacity(
                opacity: 0.8,
                child: Container(
                  width: 100,
                  height: 100,
                  decoration: BoxDecoration(
                    color: Color(0xff77797A),
                    borderRadius: BorderRadius.all(Radius.circular(20.0)),
                  ),
                  child: Column(
                    children: <Widget>[
                      Container(
//                      padding: EdgeInsets.only(right: 20, left: 20, top: 0),
                        child: Text(
                          toastShow,
                          style: TextStyle(
                            fontStyle: FontStyle.normal,
                            color: Colors.white,
                            fontSize: 14,
                          ),
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
        );
      });
      Overlay.of(context).insert(overlayEntry);
    }
  }

  startRecord() {
    print("开始录制");
  }

  stopRecord(String path, double audioTimeLength) {
    print("结束束录制");
    print("音频文件位置" + path);
    print("音频录制时长" + audioTimeLength.toString());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("仿微信发送语音"),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            new FlatButton(
                onPressed: () {
                  showView(context);
                },
                child: new Text("悬浮组件")),
            new FlatButton(
                onPressed: () {
                  if (overlayEntry != null) {
                    overlayEntry.remove();
                    overlayEntry = null;
                  }
                },
                child: new Text("隐藏悬浮组件")),
            new FlatButton(
                onPressed: () {
                  setState(() {
                    toastShow = "111";
                    if (overlayEntry != null) {
                      overlayEntry.markNeedsBuild();
                    }
                  });
                },
                child: new Text("悬浮窗状态更新")),
            new VoiceWidget(
              startRecord: startRe
Download .txt
gitextract_l9gpf37n/

├── .gitattributes
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── android/
│   ├── .gitignore
│   ├── build.gradle
│   ├── gradle/
│   │   └── wrapper/
│   │       └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── settings.gradle
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           └── kotlin/
│               └── record/
│                   └── wilson/
│                       └── flutter/
│                           └── com/
│                               └── flutter_plugin_record/
│                                   ├── FlutterPluginRecordPlugin.kt
│                                   ├── timer/
│                                   │   ├── ITimer.java
│                                   │   ├── ITimerChangeCallback.java
│                                   │   ├── MTimer.java
│                                   │   └── TimerUtils.java
│                                   └── utils/
│                                       ├── AudioHandler.java
│                                       ├── DateUtils.java
│                                       ├── DialogUtil.java
│                                       ├── FileTool.java
│                                       ├── LogUtils.java
│                                       ├── PlayState.java
│                                       ├── PlayUtilsPlus.java
│                                       └── RecorderUtil.java
├── example/
│   ├── .flutter-plugins-dependencies
│   ├── .gitignore
│   ├── .metadata
│   ├── README.md
│   ├── android/
│   │   ├── .gitignore
│   │   ├── app/
│   │   │   ├── build.gradle
│   │   │   └── src/
│   │   │       ├── debug/
│   │   │       │   └── AndroidManifest.xml
│   │   │       ├── main/
│   │   │       │   ├── AndroidManifest.xml
│   │   │       │   ├── kotlin/
│   │   │       │   │   └── record/
│   │   │       │   │       └── wilson/
│   │   │       │   │           └── flutter/
│   │   │       │   │               └── com/
│   │   │       │   │                   └── flutter_plugin_record_example/
│   │   │       │   │                       └── MainActivity.kt
│   │   │       │   └── res/
│   │   │       │       ├── drawable/
│   │   │       │       │   └── launch_background.xml
│   │   │       │       ├── values/
│   │   │       │       │   └── styles.xml
│   │   │       │       └── xml/
│   │   │       │           └── network_security_config.xml
│   │   │       └── profile/
│   │   │           └── AndroidManifest.xml
│   │   ├── build.gradle
│   │   ├── gradle/
│   │   │   └── wrapper/
│   │   │       └── gradle-wrapper.properties
│   │   ├── gradle.properties
│   │   ├── res/
│   │   │   └── values/
│   │   │       └── strings_en.arb
│   │   └── settings.gradle
│   ├── ios/
│   │   ├── Flutter/
│   │   │   ├── .last_build_id
│   │   │   ├── AppFrameworkInfo.plist
│   │   │   ├── Debug.xcconfig
│   │   │   ├── Flutter.podspec
│   │   │   └── Release.xcconfig
│   │   ├── Podfile
│   │   ├── Runner/
│   │   │   ├── AppDelegate.swift
│   │   │   ├── Assets.xcassets/
│   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   └── Contents.json
│   │   │   │   └── LaunchImage.imageset/
│   │   │   │       ├── Contents.json
│   │   │   │       └── README.md
│   │   │   ├── Base.lproj/
│   │   │   │   ├── LaunchScreen.storyboard
│   │   │   │   └── Main.storyboard
│   │   │   ├── Info.plist
│   │   │   └── Runner-Bridging-Header.h
│   │   ├── Runner.xcodeproj/
│   │   │   ├── project.pbxproj
│   │   │   ├── project.xcworkspace/
│   │   │   │   ├── contents.xcworkspacedata
│   │   │   │   └── xcshareddata/
│   │   │   │       ├── IDEWorkspaceChecks.plist
│   │   │   │       └── WorkspaceSettings.xcsettings
│   │   │   └── xcshareddata/
│   │   │       └── xcschemes/
│   │   │           └── Runner.xcscheme
│   │   └── Runner.xcworkspace/
│   │       ├── contents.xcworkspacedata
│   │       └── xcshareddata/
│   │           ├── IDEWorkspaceChecks.plist
│   │           └── WorkspaceSettings.xcsettings
│   ├── lib/
│   │   ├── generated/
│   │   │   └── i18n.dart
│   │   ├── main.dart
│   │   ├── path_provider_screen.dart
│   │   ├── record_mp3_screen.dart
│   │   ├── record_screen.dart
│   │   └── wechat_record_screen.dart
│   ├── pubspec.yaml
│   └── test/
│       └── widget_test.dart
├── flutter_plugin_record.iml
├── ios/
│   ├── .gitignore
│   ├── Assets/
│   │   └── .gitkeep
│   ├── Classes/
│   │   ├── DPAudioPlayer.h
│   │   ├── DPAudioPlayer.m
│   │   ├── DPAudioRecorder.h
│   │   ├── DPAudioRecorder.m
│   │   ├── FlutterPluginRecordPlugin.h
│   │   ├── FlutterPluginRecordPlugin.m
│   │   ├── JX_GCDTimerManager.h
│   │   └── JX_GCDTimerManager.m
│   └── flutter_plugin_record.podspec
├── lib/
│   ├── const/
│   │   ├── play_state.dart
│   │   ├── record_state.dart
│   │   └── response.dart
│   ├── flutter_plugin_record.dart
│   ├── index.dart
│   ├── utils/
│   │   └── common_toast.dart
│   └── widgets/
│       ├── custom_overlay.dart
│       └── voice_widget.dart
├── pubspec.yaml
└── test/
    └── flutter_plugin_record_test.dart
Download .txt
SYMBOL INDEX (294 symbols across 28 files)

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimer.java
  type ITimer (line 5) | public interface ITimer {
    method startTimer (line 6) | void startTimer();
    method pauseTimer (line 8) | void pauseTimer();
    method resumeTimer (line 10) | void resumeTimer();
    method stopTimer (line 12) | void stopTimer();

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimerChangeCallback.java
  type ITimerChangeCallback (line 3) | public interface ITimerChangeCallback {
    method onTimeChange (line 4) | void onTimeChange(long time);

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/MTimer.java
  class MTimer (line 9) | public class MTimer implements ITimer {
    method MTimer (line 31) | private MTimer(long initDelay, long delay, ITimerChangeCallback[] call...
    method makeTimerBuilder (line 44) | public static Builder makeTimerBuilder() {
    method startTimer (line 51) | @Override
    method pauseTimer (line 65) | @Override
    method resumeTimer (line 79) | @Override
    method stopTimer (line 93) | @Override
    method realStartTimer (line 107) | private void realStartTimer(boolean isToZero) {
    method realStopTimer (line 125) | private void realStopTimer(boolean isToZero) {
    method checkCallback (line 150) | private boolean checkCallback() {
    method createTask (line 159) | private TimerTask createTask() {
    method notifyCallback (line 175) | private void notifyCallback(AtomicLong time) {
    class Builder (line 183) | public static class Builder {
      method setTag (line 194) | public Builder setTag(String tag) {
      method setInitDelay (line 208) | public Builder setInitDelay(long initDelay) {
      method setCallbacks (line 218) | public Builder setCallbacks(ITimerChangeCallback... callbacks) {
      method setDelay (line 228) | public Builder setDelay(long delay) {
      method reset (line 236) | public void reset() {
      method build (line 246) | public MTimer build() {

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/TimerUtils.java
  class TimerUtils (line 14) | public final class TimerUtils {
    method TimerUtils (line 30) | private TimerUtils() {
    method makeBuilder (line 39) | public static MTimer.Builder makeBuilder() {
    method startTimer (line 47) | public static void startTimer(String tag) {
    method resumeTimer (line 54) | public static void resumeTimer(String tag) {
    method pauseTimer (line 61) | public static void pauseTimer(String tag) {
    method stopTimer (line 68) | public static void stopTimer(String tag) {
    method formatTime (line 78) | public static String formatTime(long cnt) {
    method addTimerToCache (line 93) | public static void addTimerToCache(String tag, MTimer timer) {
    method actionTimer (line 106) | private static void actionTimer(int action, String tag) {
    method findMTimerByTag (line 145) | private static MTimer findMTimerByTag(String tag) {
    method checkTag (line 159) | private static boolean checkTag(String tag) {

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/AudioHandler.java
  class AudioHandler (line 25) | public final class AudioHandler extends Handler {
    method createHandler (line 46) | public static AudioHandler createHandler(Frequency frequency) {
    method AudioHandler (line 52) | private AudioHandler(AudioThread thread) {
    method startRecord (line 56) | public void startRecord(RecordListener listener) {
    method isAvailable (line 63) | public boolean isAvailable() {
    method stopRecord (line 68) | public void stopRecord() {
    method cancelRecord (line 73) | public void cancelRecord() {
    method isRecording (line 78) | public boolean isRecording() {
    method release (line 83) | public void release() {
    method handleMessage (line 88) | @Override
    type Frequency (line 106) | public enum Frequency {
      method Frequency (line 114) | private Frequency(int f) {
      method getFrequency (line 118) | public int getFrequency() {
    class AudioThread (line 124) | private static final class AudioThread extends Thread {
      method initTimer (line 153) | private void initTimer() {
      method AudioThread (line 164) | private AudioThread(Frequency frequency) {
      method isRecording (line 177) | boolean isRecording() {
      method startRecord (line 189) | private void startRecord(RecordListener listener) {
      method setPauseRecord (line 278) | public void setPauseRecord() {
      method setCancelRecord (line 282) | public void setCancelRecord() {
      method getDb (line 287) | private double getDb(byte[] buffer) {
      method getShort (line 293) | private static short getShort(byte argB1, byte argB2) {
      method getDiviation (line 300) | private static double getDiviation(byte[] buffer, int start, int len...
      method StandardDiviation (line 313) | private static double StandardDiviation(double[] x) {
      method pauseRecord (line 328) | private void pauseRecord() {
      method release (line 343) | public void release() {
      method run (line 354) | @Override
      method getHandler (line 373) | AudioHandler getHandler() {
      method checkSampleRatesValid (line 389) | public boolean checkSampleRatesValid(int frequency) {
      method getValidSampleRates (line 395) | public int getValidSampleRates() {
    type RecordListener (line 408) | public interface RecordListener {
      method onStart (line 409) | void onStart();
      method getFilePath (line 411) | String getFilePath();
      method onVolume (line 413) | void onVolume(double db);
      method onStop (line 416) | void onStop(File recordFile,Double audioTime);
      method onError (line 417) | void onError(int error);
    class WaveHeaderHelper (line 421) | private static class WaveHeaderHelper {
      method writeHeader (line 423) | private static void writeHeader(OutputStream out, int sampleRate, in...
      method writeWaveHeaderLength (line 439) | private static void writeWaveHeaderLength(File f) {
      method writeInt (line 465) | private static void writeInt(final OutputStream output, final int va...
      method writeShort (line 472) | private static void writeShort(final OutputStream output, final shor...
      method writeString (line 477) | private static void writeString(final OutputStream output, final Str...

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DateUtils.java
  class DateUtils (line 6) | public class DateUtils {
    method dateToString (line 7) | public static String dateToString(Date date) {

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DialogUtil.java
  class DialogUtil (line 15) | public class DialogUtil {
    method Dialog (line 17) | public static void Dialog(final Activity activity, String content) {
    method startSetting (line 37) | public static void startSetting(Context context) {

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/FileTool.java
  class FileTool (line 31) | public final class FileTool {
    method FileTool (line 33) | private FileTool() {
    method GetFileDataBytes (line 64) | public static byte[] GetFileDataBytes(File file, int fileLen) {
    method getFileRootPathString (line 85) | public static String getFileRootPathString(Context context) {
    method getDefaultApkSavePath (line 90) | public static String getDefaultApkSavePath() {
    method getFileRootPath (line 95) | public static File getFileRootPath(Context context) {
    method copyFile2 (line 111) | public static void copyFile2(String oldFilePath, String newFilePath) {
    method isValidName (line 131) | public static boolean isValidName(String text) {
    method deleteFile (line 152) | public static void deleteFile(File f) {
    method getFileMD5String (line 164) | public static String getFileMD5String(File f) {
    method md5 (line 191) | public static String md5(String string) {
    method readPictureDegree (line 216) | public static int readPictureDegree(String path) {
    method getFileExtension (line 239) | public static String getFileExtension(File file) {
    method getFileExtension (line 250) | public static String getFileExtension(String fileName) {
    method urlDelStoken (line 259) | public static String urlDelStoken(String url) {
    method getCacheDirectory (line 310) | public static File getCacheDirectory(Context context) {
    method getCacheDirectory (line 326) | public static File getCacheDirectory(Context context, boolean preferEx...
    method getIndividualImageCacheDirectory (line 360) | public static File getIndividualImageCacheDirectory(Context context) {
    method getIndividualUploadCacheDirectory (line 364) | public static File getIndividualUploadCacheDirectory(Context context) {
    method getIndividualLogCacheDirectory (line 368) | public static File getIndividualLogCacheDirectory(Context context) {
    method getIndividualAudioCacheDirectory (line 373) | public static File getIndividualAudioCacheDirectory(Context context) {
    method getIndividualImageFileDirectory (line 379) | public static File getIndividualImageFileDirectory(Context context) {
    method getIndividualCacheDirectory (line 394) | public static File getIndividualCacheDirectory(Context context, String...
    method getOwnCacheDirectory (line 420) | public static File getOwnCacheDirectory(Context context, String cacheD...
    method getOwnCacheDirectory (line 441) | public static File getOwnCacheDirectory(Context context, String cacheD...
    method getDiskSizeRemain (line 454) | @SuppressLint("NewApi")
    method getExternalCacheDir (line 470) | private static File getExternalCacheDir(Context context) {
    method hasExternalStoragePermission (line 486) | private static boolean hasExternalStoragePermission(Context context) {
    method closeIO (line 492) | public static void closeIO(Closeable... io) {
    method getMimeType (line 505) | public static String getMimeType(String path) {
    method getMimeType (line 533) | public static String getMimeType(File file) {
    method getMimeType1 (line 544) | public static String getMimeType1(String path) {
    method getMimeTypeByExtension (line 555) | private static String getMimeTypeByExtension(String extension) {
    method getDefaultSavePath (line 561) | public static String getDefaultSavePath() {
    method isExternalStorageWritable (line 570) | public static boolean isExternalStorageWritable() {
    method isExternalStorageReadable (line 579) | public static boolean isExternalStorageReadable() {
    method getFolderSize (line 588) | public static long getFolderSize(File file) {
    method deleteFolderFile (line 602) | public static void deleteFolderFile(String filePath, boolean deleteThi...

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/LogUtils.java
  class LogUtils (line 33) | public class LogUtils {
    method makeLogTag (line 40) | public static String makeLogTag(String str) {
    method makeLogTag (line 50) | public static String makeLogTag(Class cls) {
    method getTAG (line 54) | public static String getTAG() {
    method LOGD (line 58) | public static void LOGD(String message) {
    method LOGD (line 64) | public static void LOGD(final String tag, String message) {
    method LOGDPrintProcess (line 72) | public static void LOGDPrintProcess(final String tag, String message) {
    method LOGD (line 81) | public static void LOGD(final String tag, String message, Throwable ca...
    method LOGV (line 89) | public static void LOGV(String message) {
    method LOGV (line 95) | public static void LOGV(final String tag, String message) {
    method LOGV (line 103) | public static void LOGV(final String tag, String message, Throwable ca...
    method LOGI (line 111) | public static void LOGI(String message) {
    method LOGI (line 117) | public static void LOGI(final String tag, String message) {
    method LOGI (line 123) | public static void LOGI(final String tag, String message, Throwable ca...
    method LOGW (line 129) | public static void LOGW(String message) {
    method LOGW (line 135) | public static void LOGW(final String tag, String message) {
    method LOGW (line 141) | public static void LOGW(final String tag, String message, Throwable ca...
    method LOGE (line 147) | public static void LOGE(String message) {
    method LOGE (line 153) | public static void LOGE(final String tag, String message) {
    method LOGEPrintProcess (line 161) | public static void LOGEPrintProcess(final String tag, String message) {
    method getLogMessage (line 170) | private static String getLogMessage(String msg) {
    method addPrintBottom (line 185) | private static void addPrintBottom(StringBuilder sb) {
    method getLine (line 193) | private static String getLine(String s) {
    method getFullFillTopLine (line 210) | private static String getFullFillTopLine(String... s) {
    method getFullFillBottomLine (line 214) | private static String getFullFillBottomLine(String... s) {
    method getFullFillLine (line 218) | private static String getFullFillLine(int position, String... s) {
    method fillLine (line 258) | private static void fillLine(StringBuilder sb, int length, String fill...
    method addPrintHead (line 265) | private static void addPrintHead(StringBuilder sb) {
    method LOGE (line 272) | public static void LOGE(final String tag, String message, Throwable ca...
    method write2Log (line 278) | public static void write2Log(Context context, String msg) {
    method getErrorPrintStream (line 282) | public static PrintStream getErrorPrintStream(Context context) throws ...
    method write2Log (line 287) | public static void write2Log(Context context, String msg, String name) {
    method getTag (line 305) | private static String getTag() {
    method LogUtils (line 321) | private LogUtils() {
    method getIntervalCounter (line 324) | public static IntervalCounter getIntervalCounter() {
    class IntervalCounter (line 328) | public static class IntervalCounter {
      method IntervalCounter (line 331) | private IntervalCounter() {
      method getInterval (line 335) | public long getInterval() {
      method getTimeStamps (line 341) | public long getTimeStamps() {
      method getIntervalStr (line 346) | public String getIntervalStr() {
    method getCurCpuFreq (line 351) | public static String getCurCpuFreq() {

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayState.java
  type PlayState (line 3) | public enum PlayState {

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayUtilsPlus.java
  class PlayUtilsPlus (line 6) | public class PlayUtilsPlus {
    method PlayUtilsPlus (line 10) | public PlayUtilsPlus() {
    method setPlayStateChangeListener (line 13) | public void setPlayStateChangeListener(PlayStateChangeListener listene...
    method startPlaying (line 18) | public void startPlaying(String filePath) {
    method pausePlay (line 45) | public boolean pausePlay() {
    method stopPlaying (line 61) | public void stopPlaying() {
    method isPlaying (line 77) | public boolean isPlaying() {
    type PlayStateChangeListener (line 85) | public interface PlayStateChangeListener {
      method onPlayStateChange (line 86) | void onPlayStateChange(PlayState playState);

FILE: android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/RecorderUtil.java
  class RecorderUtil (line 16) | public class RecorderUtil {
    method RecorderUtil (line 26) | public RecorderUtil() {
    method RecorderUtil (line 32) | public RecorderUtil(String path) {
    method addPlayAmplitudeListener (line 40) | public void addPlayAmplitudeListener(RecordListener recordListener) {
    method addPlayStateListener (line 43) | public void addPlayStateListener(PlayStateListener playStateListener) {
    method initVoice (line 47) | private void initVoice() {
    method initPath (line 56) | private void initPath() {
    method initVoicePath (line 73) | private void initVoicePath() {
    method initRecorder (line 83) | private void initRecorder() {
    method startRecord (line 100) | public void startRecord() {
    method stopRecord (line 109) | public void stopRecord() {
    method playVoice (line 113) | public void playVoice() {
    method pausePlay (line 129) | public  boolean pausePlay(){
    method stopPlay (line 135) | public  void stopPlay(){
    type RecordListener (line 140) | public interface RecordListener {
      method onPlayAmplitude (line 141) | void onPlayAmplitude(Double amplitude);
      method onVoicePathSuccess (line 143) | void onVoicePathSuccess(String voicePath);
    type PlayStateListener (line 147) | public interface PlayStateListener {
      method playState (line 149) | void playState(PlayState playState);

FILE: example/lib/generated/i18n.dart
  class S (line 11) | class S implements WidgetsLocalizations {
    method of (line 19) | S of(BuildContext context)
  class $en (line 26) | class $en extends S {
  class GeneratedLocalizationsDelegate (line 30) | class GeneratedLocalizationsDelegate extends LocalizationsDelegate<S> {
    method listResolution (line 39) | LocaleListResolutionCallback listResolution({Locale fallback, bool wit...
    method resolution (line 49) | LocaleResolutionCallback resolution({Locale fallback, bool withCountry...
    method load (line 56) | Future<S> load(Locale locale)
    method isSupported (line 72) | bool isSupported(Locale locale)
    method shouldReload (line 75) | bool shouldReload(GeneratedLocalizationsDelegate old)
    method _resolve (line 80) | Locale _resolve(Locale locale, Locale fallback, Iterable<Locale> suppo...
    method _isSupported (line 99) | bool _isSupported(Locale locale, bool withCountry)
  function getLang (line 122) | String getLang(Locale l)

FILE: example/lib/main.dart
  function main (line 7) | void main()
  class MyApp (line 9) | class MyApp extends StatelessWidget {
    method build (line 11) | Widget build(BuildContext context)
  class MyHomePage (line 30) | class MyHomePage extends StatefulWidget {
    method createState (line 35) | _MyHomePageState createState()
  class _MyHomePageState (line 38) | class _MyHomePageState extends State<MyHomePage> {
    method initState (line 40) | void initState()
    method build (line 45) | Widget build(BuildContext context)

FILE: example/lib/path_provider_screen.dart
  class PathProviderScreen (line 6) | class PathProviderScreen extends StatefulWidget {
    method createState (line 11) | _PathProviderScreenState createState()
  class _PathProviderScreenState (line 14) | class _PathProviderScreenState extends State<PathProviderScreen> {
    method _requestTempDirectory (line 23) | void _requestTempDirectory()
    method _buildDirectory (line 29) | Widget _buildDirectory(
    method _buildDirectories (line 44) | Widget _buildDirectories(
    method _requestAppDocumentsDirectory (line 61) | void _requestAppDocumentsDirectory()
    method _requestAppSupportDirectory (line 67) | void _requestAppSupportDirectory()
    method _requestAppLibraryDirectory (line 73) | void _requestAppLibraryDirectory()
    method _requestExternalStorageDirectory (line 79) | void _requestExternalStorageDirectory()
    method _requestExternalStorageDirectories (line 85) | void _requestExternalStorageDirectories(StorageDirectory type)
    method _requestExternalCacheDirectories (line 91) | void _requestExternalCacheDirectories()
    method build (line 98) | Widget build(BuildContext context)

FILE: example/lib/record_mp3_screen.dart
  class RecordMp3Screen (line 8) | class RecordMp3Screen extends StatefulWidget {
    method createState (line 10) | _RecordMp3ScreenState createState()
  class _RecordMp3ScreenState (line 13) | class _RecordMp3ScreenState extends State<RecordMp3Screen> {
    method initState (line 19) | void initState()
    method build (line 58) | Widget build(BuildContext context)
    method _requestAppDocumentsDirectory (line 126) | void _requestAppDocumentsDirectory()
    method _initRecordMp3 (line 156) | void _initRecordMp3()
    method start (line 161) | void start()
    method startByWavPath (line 166) | void startByWavPath(String wavPath)
    method stop (line 171) | void stop()
    method play (line 176) | void play()
    method playByPath (line 181) | void playByPath(String path,String type)
    method pause (line 186) | void pause()
    method dispose (line 191) | void dispose()
    method stopPlay (line 197) | void stopPlay()

FILE: example/lib/record_screen.dart
  class RecordScreen (line 8) | class RecordScreen extends StatefulWidget {
    method createState (line 10) | _RecordScreenState createState()
  class _RecordScreenState (line 13) | class _RecordScreenState extends State<RecordScreen> {
    method initState (line 19) | void initState()
    method build (line 58) | Widget build(BuildContext context)
    method _requestAppDocumentsDirectory (line 126) | void _requestAppDocumentsDirectory()
    method _init (line 151) | void _init()
    method start (line 156) | void start()
    method startByWavPath (line 161) | void startByWavPath(String wavPath)
    method stop (line 166) | void stop()
    method play (line 171) | void play()
    method playByPath (line 176) | void playByPath(String path,String type)
    method pause (line 181) | void pause()
    method dispose (line 186) | void dispose()
    method stopPlay (line 192) | void stopPlay()

FILE: example/lib/wechat_record_screen.dart
  class WeChatRecordScreen (line 4) | class WeChatRecordScreen extends StatefulWidget {
    method createState (line 6) | _WeChatRecordScreenState createState()
  class _WeChatRecordScreenState (line 9) | class _WeChatRecordScreenState extends State<WeChatRecordScreen> {
    method build (line 66) | Widget build(BuildContext context)

FILE: example/test/widget_test.dart
  function main (line 13) | void main()

FILE: ios/Classes/JX_GCDTimerManager.h
  type ActionOption (line 4) | typedef enum : NSUInteger {

FILE: lib/const/play_state.dart
  class PlayState (line 1) | class PlayState {

FILE: lib/const/record_state.dart
  class RecordState (line 1) | class RecordState {

FILE: lib/const/response.dart
  class RecordResponse (line 1) | class RecordResponse {

FILE: lib/flutter_plugin_record.dart
  class FlutterPluginRecord (line 8) | class FlutterPluginRecord {
    method _invokeMethod (line 22) | Future<dynamic> _invokeMethod(String method,
    method _handler (line 57) | Future<dynamic> _handler(MethodCall methodCall)
    method init (line 136) | Future init()
    method initRecordMp3 (line 143) | Future initRecordMp3()
    method start (line 149) | Future start()
    method startByWavPath (line 155) | Future startByWavPath(String wavPath)
    method stop (line 161) | Future stop()
    method play (line 167) | Future play()
    method playByPath (line 189) | Future playByPath(String path, String type)
    method pausePlay (line 198) | Future pausePlay()
    method stopPlay (line 205) | Future stopPlay()

FILE: lib/utils/common_toast.dart
  class CommonToast (line 6) | class CommonToast {
    method showView (line 7) | showView({
    method removeOverlay (line 19) | void removeOverlay()

FILE: lib/widgets/custom_overlay.dart
  class CustomOverlay (line 3) | class CustomOverlay extends StatelessWidget {
    method build (line 20) | Widget build(BuildContext context)

FILE: lib/widgets/voice_widget.dart
  type startRecord (line 9) | typedef startRecord = Future Function();
  type stopRecord (line 10) | typedef stopRecord = Future Function();
  class VoiceWidget (line 12) | class VoiceWidget extends StatefulWidget {
    method createState (line 30) | _VoiceWidgetState createState()
  class _VoiceWidgetState (line 33) | class _VoiceWidgetState extends State<VoiceWidget> {
    method initState (line 51) | void initState()
    method _init (line 218) | void _init()
    method start (line 223) | void start()
    method stop (line 228) | void stop()
    method build (line 233) | Widget build(BuildContext context)
    method dispose (line 274) | void dispose()

FILE: test/flutter_plugin_record_test.dart
  function main (line 4) | void main()
Condensed preview — 95 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (275K chars).
[
  {
    "path": ".gitattributes",
    "chars": 112,
    "preview": "*.yml linguist-language=Dart\n*.java linguist-language=Dart\n*.m linguist-language=Dart\n*.h linguist-language=Dart"
  },
  {
    "path": ".gitignore",
    "chars": 53,
    "preview": ".DS_Store\n.dart_tool/\n\n.packages\n.pub/\n\nbuild/\n.idea/"
  },
  {
    "path": ".metadata",
    "chars": 308,
    "preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1019,
    "preview": "## 1.0.1\n\n* 录音时长类型修改为 Double(和 iOS 端保持一致)\n\n## 1.0.0\n\n*  添加空安全相关逻辑\n*  适配 flutter 2.0\n\n## 0.3.5\n\n*  修复ios 音频监听灵敏性问题\n\n## 0."
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 5944,
    "preview": "[TOC]\n\n\n# 使用Flutter实现 仿微信录音的插件 \n插件支持android 和IOS\n\n\n-------\n插件提供的功能\n录制\n1. 录制语音,\n2. 播放录音,\n3. 录制声音大小的监听\n4. 提供录制时长的监听\n5. 提供类"
  },
  {
    "path": "android/.gitignore",
    "chars": 97,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n"
  },
  {
    "path": "android/build.gradle",
    "chars": 1164,
    "preview": "group 'record.wilson.flutter.com.flutter_plugin_record'\nversion '1.0-SNAPSHOT'\n\nbuildscript {\n//    ext.kotlin_version ="
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "chars": 203,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dist"
  },
  {
    "path": "android/gradle.properties",
    "chars": 30,
    "preview": "org.gradle.jvmargs=-Xmx1536M\n\n"
  },
  {
    "path": "android/settings.gradle",
    "chars": 43,
    "preview": "rootProject.name = 'flutter_plugin_record'\n"
  },
  {
    "path": "android/src/main/AndroidManifest.xml",
    "chars": 362,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  package=\"record.wilson.flutter.com.flutter_plugin"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/FlutterPluginRecordPlugin.kt",
    "chars": 16142,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record\n\nimport android.Manifest\nimport android.app.Activity\nimport andr"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimer.java",
    "chars": 189,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\n\n\npublic interface ITimer {\n    void startTimer();\n\n    "
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimerChangeCallback.java",
    "chars": 139,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\npublic interface ITimerChangeCallback {\n    void onTimeC"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/MTimer.java",
    "chars": 5922,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\nimport android.text.TextUtils;\n\nimport java.util.Timer;\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/TimerUtils.java",
    "chars": 3803,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\nimport android.text.TextUtils;\nimport android.util.Log;\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/AudioHandler.java",
    "chars": 17198,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.media.AudioFormat;\nimport android.media.A"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DateUtils.java",
    "chars": 383,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DialogUtil.java",
    "chars": 1840,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.app.Activity;\nimport android.app.Dialog;\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/FileTool.java",
    "chars": 24664,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\r\n\r\nimport android.annotation.SuppressLint;\r\nimport androi"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/LogUtils.java",
    "chars": 11631,
    "preview": "/*\r\n * Copyright 2014 Google Inc. All rights reserved.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayState.java",
    "chars": 126,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\npublic enum PlayState {\n    prepare, start, pause, compl"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayUtilsPlus.java",
    "chars": 2573,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.media.MediaPlayer;\n\n\npublic class PlayUti"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/RecorderUtil.java",
    "chars": 3950,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.os.Environment;\nimport android.util.Log;\n"
  },
  {
    "path": "example/.flutter-plugins-dependencies",
    "chars": 2762,
    "preview": "{\"info\":\"This is a generated file; do not edit or check into version control.\",\"plugins\":{\"ios\":[{\"name\":\"flutter_plugin"
  },
  {
    "path": "example/.gitignore",
    "chars": 1491,
    "preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.i"
  },
  {
    "path": "example/.metadata",
    "chars": 305,
    "preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
  },
  {
    "path": "example/README.md",
    "chars": 2669,
    "preview": "# flutter_plugin_record_example\n\n\n### 使用\n\n### 1, 初始化录制\n\n\n\n可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化\n\n    //实例化对象 \n    Flutt"
  },
  {
    "path": "example/android/.gitignore",
    "chars": 262,
    "preview": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n\n# Remembe"
  },
  {
    "path": "example/android/app/build.gradle",
    "chars": 2057,
    "preview": "def localProperties = new Properties()\ndef localPropertiesFile = rootProject.file('local.properties')\nif (localPropertie"
  },
  {
    "path": "example/android/app/src/debug/AndroidManifest.xml",
    "chars": 363,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"record.wilson.flutter.com.flutter_plug"
  },
  {
    "path": "example/android/app/src/main/AndroidManifest.xml",
    "chars": 2057,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n"
  },
  {
    "path": "example/android/app/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record_example/MainActivity.kt",
    "chars": 161,
    "preview": "package record.wilson.flutter.com.flutter_plugin_record_example\n\nimport io.flutter.embedding.android.FlutterActivity\n\ncl"
  },
  {
    "path": "example/android/app/src/main/res/drawable/launch_background.xml",
    "chars": 434,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
  },
  {
    "path": "example/android/app/src/main/res/values/styles.xml",
    "chars": 361,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTi"
  },
  {
    "path": "example/android/app/src/main/res/xml/network_security_config.xml",
    "chars": 145,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\" />\n</"
  },
  {
    "path": "example/android/app/src/profile/AndroidManifest.xml",
    "chars": 363,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"record.wilson.flutter.com.flutter_plug"
  },
  {
    "path": "example/android/build.gradle",
    "chars": 619,
    "preview": "buildscript {\n//    ext.kotlin_version = '1.2.71'\n    ext.kotlin_version = '1.3.50'\n\n    repositories {\n        google()"
  },
  {
    "path": "example/android/gradle/wrapper/gradle-wrapper.properties",
    "chars": 232,
    "preview": "#Wed Dec 18 09:02:45 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "example/android/gradle.properties",
    "chars": 105,
    "preview": "org.gradle.jvmargs=-Xmx1536M\n\nandroid.enableR8=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n"
  },
  {
    "path": "example/android/res/values/strings_en.arb",
    "chars": 2,
    "preview": "{}"
  },
  {
    "path": "example/android/settings.gradle",
    "chars": 484,
    "preview": "include ':app'\n\ndef flutterProjectRoot = rootProject.projectDir.parentFile.toPath()\n\ndef plugins = new Properties()\ndef "
  },
  {
    "path": "example/ios/Flutter/.last_build_id",
    "chars": 32,
    "preview": "dcb78416cfbfe60288e8c774d67c139c"
  },
  {
    "path": "example/ios/Flutter/AppFrameworkInfo.plist",
    "chars": 794,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/Flutter/Debug.xcconfig",
    "chars": 106,
    "preview": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "example/ios/Flutter/Flutter.podspec",
    "chars": 803,
    "preview": "#\n# NOTE: This podspec is NOT to be published. It is only used as a local source!\n#       This is a generated file; do n"
  },
  {
    "path": "example/ios/Flutter/Release.xcconfig",
    "chars": 108,
    "preview": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "example/ios/Podfile",
    "chars": 1354,
    "preview": "# Uncomment this line to define a global platform for your project\n# platform :ios, '9.0'\n\n# CocoaPods analytics sends n"
  },
  {
    "path": "example/ios/Runner/AppDelegate.swift",
    "chars": 404,
    "preview": "import UIKit\nimport Flutter\n\n@UIApplicationMain\n@objc class AppDelegate: FlutterAppDelegate {\n  override func applicatio"
  },
  {
    "path": "example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 2519,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n   "
  },
  {
    "path": "example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "chars": 391,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n  "
  },
  {
    "path": "example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "chars": 336,
    "preview": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in"
  },
  {
    "path": "example/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "chars": 2377,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "example/ios/Runner/Base.lproj/Main.storyboard",
    "chars": 1605,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "example/ios/Runner/Info.plist",
    "chars": 1713,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/Runner/Runner-Bridging-Header.h",
    "chars": 37,
    "preview": "#import \"GeneratedPluginRegistrant.h\""
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.pbxproj",
    "chars": 22677,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 152,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodepr"
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "chars": 226,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "chars": 3291,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1020\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "example/ios/Runner.xcworkspace/contents.xcworkspacedata",
    "chars": 224,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodepr"
  },
  {
    "path": "example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "chars": 226,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/lib/generated/i18n.dart",
    "chars": 3548,
    "preview": "import 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\n\n// ignore_for_f"
  },
  {
    "path": "example/lib/main.dart",
    "chars": 2456,
    "preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record_example/path_provider_screen.dart';\nimport"
  },
  {
    "path": "example/lib/path_provider_screen.dart",
    "chars": 6404,
    "preview": "import 'dart:io';\n\nimport 'package:flutter/material.dart';\nimport 'package:path_provider/path_provider.dart';\n\nclass Pat"
  },
  {
    "path": "example/lib/record_mp3_screen.dart",
    "chars": 4832,
    "preview": "import 'dart:io';\n\nimport 'package:flustars/flustars.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flut"
  },
  {
    "path": "example/lib/record_screen.dart",
    "chars": 4632,
    "preview": "import 'dart:io';\n\nimport 'package:flustars/flustars.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flut"
  },
  {
    "path": "example/lib/wechat_record_screen.dart",
    "chars": 3158,
    "preview": "import 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/index.dart';\n\nclass WeChatRecordScreen ext"
  },
  {
    "path": "example/pubspec.yaml",
    "chars": 3105,
    "preview": "name: flutter_plugin_record_example\ndescription: The flutter voice recording plug-in,provides the recording animation an"
  },
  {
    "path": "example/test/widget_test.dart",
    "chars": 921,
    "preview": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester"
  },
  {
    "path": "flutter_plugin_record.iml",
    "chars": 1774,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\" "
  },
  {
    "path": "ios/.gitignore",
    "chars": 398,
    "preview": ".idea/\n.vagrant/\n.sconsign.dblite\n.svn/\n\n.DS_Store\n*.swp\nprofile\n\nDerivedData/\nbuild/\nGeneratedPluginRegistrant.h\nGenera"
  },
  {
    "path": "ios/Assets/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ios/Classes/DPAudioPlayer.h",
    "chars": 514,
    "preview": "\n#import <Foundation/Foundation.h>\n\ntypedef void(^PlayCompleteBlock)(void);\n\ntypedef void(^StartPlayingBlock)(BOOL isPla"
  },
  {
    "path": "ios/Classes/DPAudioPlayer.m",
    "chars": 5110,
    "preview": "\n#import \"DPAudioPlayer.h\"\n#import <AVFoundation/AVFoundation.h>\n#import <UIKit/UIKit.h>\n\n@interface DPAudioPlayer () <A"
  },
  {
    "path": "ios/Classes/DPAudioRecorder.h",
    "chars": 908,
    "preview": "\n#import <Foundation/Foundation.h>\n\ntypedef void(^AudioRecorderFinishRecordingBlock)(NSData *data, NSTimeInterval audioT"
  },
  {
    "path": "ios/Classes/DPAudioRecorder.m",
    "chars": 10155,
    "preview": "\n#import \"DPAudioRecorder.h\"\n#import \"DPAudioPlayer.h\"\n#import <AVFoundation/AVFoundation.h>\n#import <UIKit/UIKit.h>\n#im"
  },
  {
    "path": "ios/Classes/FlutterPluginRecordPlugin.h",
    "chars": 97,
    "preview": "#import <Flutter/Flutter.h>\n\n@interface FlutterPluginRecordPlugin : NSObject<FlutterPlugin>\n@end\n"
  },
  {
    "path": "ios/Classes/FlutterPluginRecordPlugin.m",
    "chars": 8438,
    "preview": "#import \"FlutterPluginRecordPlugin.h\"\n#import \"DPAudioRecorder.h\"\n#import \"DPAudioPlayer.h\"\n\n\n@implementation FlutterPlu"
  },
  {
    "path": "ios/Classes/JX_GCDTimerManager.h",
    "chars": 1084,
    "preview": "\n#import <Foundation/Foundation.h>\n\ntypedef enum : NSUInteger {\n    AbandonPreviousAction, // 废除之前的任务\n    MergePreviousA"
  },
  {
    "path": "ios/Classes/JX_GCDTimerManager.m",
    "chars": 4434,
    "preview": "\n#import \"JX_GCDTimerManager.h\"\n\n@interface JX_GCDTimerManager()\n\n@property (nonatomic, strong) NSMutableDictionary *tim"
  },
  {
    "path": "ios/flutter_plugin_record.podspec",
    "chars": 792,
    "preview": "#\n# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html\n#\n#use_frameworks!\nPod::Spec.new d"
  },
  {
    "path": "lib/const/play_state.dart",
    "chars": 105,
    "preview": "class PlayState {\n  String playState;\n  String playPath;\n\n  PlayState(this.playState, this.playPath);\n\n}\n"
  },
  {
    "path": "lib/const/record_state.dart",
    "chars": 506,
    "preview": "class RecordState {\n  ///发送到原生端的方法名\n  static String init = \"init\";\n  static String start = \"start\";\n  static String star"
  },
  {
    "path": "lib/const/response.dart",
    "chars": 276,
    "preview": "class RecordResponse {\n  bool? success;\n  String? path;\n  String? msg;\n  String? key;\n  double? audioTimeLength;\n\n  Reco"
  },
  {
    "path": "lib/flutter_plugin_record.dart",
    "chars": 6191,
    "preview": "import 'dart:async';\n\nimport 'package:flutter/services.dart';\nimport 'package:flutter_plugin_record/const/play_state.dar"
  },
  {
    "path": "lib/index.dart",
    "chars": 179,
    "preview": "export 'const/record_state.dart';\nexport 'flutter_plugin_record.dart';\nexport 'utils/common_toast.dart';\nexport 'widgets"
  },
  {
    "path": "lib/utils/common_toast.dart",
    "chars": 1914,
    "preview": "import 'dart:async';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/widgets/custom_overl"
  },
  {
    "path": "lib/widgets/custom_overlay.dart",
    "chars": 1012,
    "preview": "import 'package:flutter/material.dart';\n\nclass CustomOverlay extends StatelessWidget {\n  final Widget? icon;\n  final Box"
  },
  {
    "path": "lib/widgets/voice_widget.dart",
    "chars": 7247,
    "preview": "import 'dart:async';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/flutter_plugin_recor"
  },
  {
    "path": "pubspec.yaml",
    "chars": 2640,
    "preview": "\nname: flutter_plugin_record\ndescription: The flutter voice recording plug-in,provides the recording animation and the r"
  },
  {
    "path": "test/flutter_plugin_record_test.dart",
    "chars": 475,
    "preview": "import 'package:flutter/services.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const MethodCh"
  }
]

About this extraction

This page contains the full source code of the yxwandroid/flutter_plugin_record GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 95 files (241.5 KB), approximately 63.6k tokens, and a symbol index with 294 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!