[
  {
    "path": ".gitattributes",
    "content": "*.yml linguist-language=Dart\n*.java linguist-language=Dart\n*.m linguist-language=Dart\n*.h linguist-language=Dart"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.dart_tool/\n\n.packages\n.pub/\n\nbuild/\n.idea/"
  },
  {
    "path": ".metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: 68587a0916366e9512a78df22c44163d041dd5f3\n  channel: stable\n\nproject_type: plugin\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 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.3.4\n\n*  修复根据路径录制bug\n\n## 0.3.3\n\n*  修复根据路径录制bug\n\n## 0.3.2\n\n*  修复android 获取权限bug\n\n## 0.3.1\n\n*  更新IOS podfile\n\n## 0.3.0\n*   添加支持录制mp3\n\n## 0.2.5\n*   修复权限申请bug\n## 0.2.4\n*   修复播放音频文件的bug。\n## 0.2.3\n*   修复bug\n## 0.2.2\n*   修复bug\n## 0.2.1\n*   补充说明文档\n## 0.2.0\n*   实现暂停播放和继续播放功能\n## 0.1.9\n\n*   注释掉初始化插件就申请权限的问题\n\n## 0.1.8\n\n*   添加支持 Android 和IOS 播放在线wav音频\n   \n## 0.1.7\n\n*   修复bug在未使用录音功能前，通过playByPath播发音频，音频可以正常播放，但无法监听到播放结束\n   \n## 0.1.6\n\n*   添加android 在开始录制时进行权限验证判断\n## 0.1.5\n\n*   实现根据传递的路径进行语音录制\n\n## 0.1.4\n\n*  解决 android 申请权限失败问题\n*  解决 快速点击发送语音录制停止不了问题\n\n## 0.1.3\n\n*  实现播放完成的回调监听\n\n\n## 0.1.2\n\n*  实现播放指定路径录音文件\n## 0.1.1\n\n*  格式代码\n## 0.1.0\n\n*  修复提示bug\n\n## 0.0.9\n\n*  添加记录录制时间的功能\n\n## 0.0.8\n\n*  重构项目为oc项目 解决播放oc 工程无法使用问题\n## 0.0.7\n\n*  更新readme\n\n## 0.0.6\n\n*  适配android 9.0 \n*  解决ios集成到oc项目不成功问题 \n\n## 0.0.5\n\n*  添加 readme 说明 \n\n## 0.0.4\n\n*  添加example readme \n\n## 0.0.3\n\n*  添加引入方式 \n\n## 0.0.2\n\n* 添加license\n\n## 0.0.1\n\n* 初始化项目 \n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "[TOC]\n\n\n# 使用Flutter实现 仿微信录音的插件 \n插件支持android 和IOS\n\n\n-------\n插件提供的功能\n录制\n1. 录制语音,\n2. 播放录音,\n3. 录制声音大小的监听\n4. 提供录制时长的监听\n5. 提供类似微信的录制组件\n6. 提供播放音频结束的监听\n7. 提供根据传递的路径进行语音录制\n8. 提供录制wav,mp3格式  具体可参考example\n\n播放\n1. 提供播放指定路径的音频文件\n2. 提供播放指定Url地址的wav,MP3格式文件\n3. 提供播放完成的回调监听\n4. 提供暂停和继续播放的功能\n5. 提供停止播放的功能\n\n\n\n\n## 1,引入\n在pubspec.yaml 文件上引入如下配置\n\n\n    引入方式1(引入最新的版本)\n    flutter_plugin_record:\n        git:\n          url: https://github.com/yxwandroid/flutter_plugin_record.git\n    \n    引入方式2 (引入指定某次commit)\n    flutter_plugin_record:\n        git:\n          url: https://github.com/yxwandroid/flutter_plugin_record.git\n          ref: 29c02b15835907879451ad9f8f88c357149c6085\n          \n    引入方式3 (引入Flutter仓库的library)\n          \n    dependencies:\n      flutter_plugin_record: ^1.0.1\n              \n              \n          \n          \n          \n          \n        \n\n\n### 使用\n### 1, 初始化录制\n#### 1.1, 初始化录制(wav)\n可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化\n\n    //实例化对象 \n    FlutterPluginRecord   recordPlugin = new FlutterPluginRecord();\n    //    初始化\n    recordPlugin.init()\n   \n\n#### 1.2, 初始化录制(Mp3)\n可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化\n\n    //实例化对象 \n    FlutterPluginRecord   recordPlugin = new FlutterPluginRecord();\n    //    初始化\n    recordPlugin.initRecordMp3()\n\n### 2, 开始录制\n   \n     recordPlugin.start()\n     \n### 3, 停止录制\n\n     recordPlugin.stop()\n     \n### 4, 播放\n\n#### 1,播放\n     \n     recordPlugin.play()\n     \n#### 2, 暂停和继续播放\n       \n     recordPlugin.pausePlay();\n\n#### 3, 停止播放\n    \n     recordPlugin.stopPlay();\n          \n### 5, 根据传递的路径进行语音录制\n\n     recordPlugin.startByWavPath(wavPath);\n     \n### 6, 根据传递的路径或则Url进行语音播放\n\n     \n      ///\n      /// 参数 path  播放音频的地址\n      ///\n      ///path 为 url类型对应的在线播放地址   https://linjuli-app-audio.oss-cn-hangzhou.aliyuncs.com/audio/50c39c768b534ce1ba25d837ed153824.wav\n      ///path 对应本地文件路径对应的是本地文件播放肚子   /sdcard/flutterdemo/wiw.wav\n      /// 参数  type\n      /// 当path 为url   type为 url\n      /// 当path 为本地地址 type为 file\n      ///\n      Future playByPath(String path, String type) async {\n        return await _invokeMethod('playByPath', <String, String>{\n          \"play\": \"play\",\n          \"path\": path,\n          \"type\": type,\n        });\n      }   \n\n### 7, 释放资源\n可以在页面退出的时候进行资源释放 比如在  dispose方法中调用如下代码\n\n     recordPlugin.dispose()\n     \n     \n     \n### 4,回调监听  \n1,初始化回调监听  \n\n  \n    ///初始化方法的监听\n    recordPlugin.responseFromInit.listen((data) {\n      if (data) {\n        print(\"初始化成功\");\n      } else {\n        print(\"初始化失败\");\n      }\n    });\n    \n\n2,开始录制停止录制监听\n\n     /// 开始录制或结束录制的监听\n        recordPlugin.response.listen((data) {\n          if (data.msg == \"onStop\") {\n            ///结束录制时会返回录制文件的地址方便上传服务器\n            print(\"onStop  \" + data.path);\n          } else if (data.msg == \"onStart\") {\n            print(\"onStart --\");\n          }\n        });\n    \n3,录制声音大小回调监听\n\n\n     ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式\n        recordPlugin.responseFromAmplitude.listen((data) {\n          var voiceData = double.parse(data.msg);\n          var tempVoice = \"\";\n          if (voiceData > 0 && voiceData < 0.1) {\n            tempVoice = \"images/voice_volume_2.png\";\n          } else if (voiceData > 0.2 && voiceData < 0.3) {\n            tempVoice = \"images/voice_volume_3.png\";\n          } else if (voiceData > 0.3 && voiceData < 0.4) {\n            tempVoice = \"images/voice_volume_4.png\";\n          } else if (voiceData > 0.4 && voiceData < 0.5) {\n            tempVoice = \"images/voice_volume_5.png\";\n          } else if (voiceData > 0.5 && voiceData < 0.6) {\n            tempVoice = \"images/voice_volume_6.png\";\n          } else if (voiceData > 0.6 && voiceData < 0.7) {\n            tempVoice = \"images/voice_volume_7.png\";\n          } else if (voiceData > 0.7 && voiceData < 1) {\n            tempVoice = \"images/voice_volume_7.png\";\n          }\n          setState(() {\n            voiceIco = tempVoice;\n            if(overlayEntry!=null){\n              overlayEntry.markNeedsBuild();\n            }\n          });\n    \n          print(\"振幅大小   \" + voiceData.toString() + \"  \" + voiceIco);\n        });\n    \n    \n    \n    \n    \n    \n    \n       \n   \n4,播放声音完成的监听监听\n     \n   \n      recordPlugin.responsePlayStateController.listen((data){\n      print(\"播放路径   \" + data.playPath );\n      print(\"播放状态   \" + data.playState );\n    });\n    \n    \n    \n## 2,录制组件的使用\n\n组件使用效果\n\nandroid效果\n\n<!--![](.README_images/video2gif_20191118_101627.gif)-->\n<img src=\"README_images/video2gif_20191118_101627.gif\" width=\"400\"  align=center />\n\nIOS效果\n\n<!--![](.README_images/ios.gif)-->\n<img src=\"README_images/ios.gif\" width=\"400\"  align=center />\n\n\n\n\n\n### 1,在使用的页面进行导入package\n\n    import 'package:flutter_plugin_record/index.dart';  \n        \n    \n    \n\n    \n### 2,在使用的地方引入VoiceWidget组件\n    \n    VoiceWidget(),\n    \n    \n    VoiceWidget({startRecord: Function, stopRecord: Function}) {\n    \n   \n    \nstartRecord 开始录制的回调 \n\nstopRecord 停止录制的回调 返回的path是录制成功之后文件的保存地址\n\n    \n     \n## IOS配置注意事项\n \n### ios集成的的时候需要在info.list添加 \n \n     \n     <key>NSMicrophoneUsageDescription</key>\n            <string>打开话筒</string>\n\n\n     <key>NSAppTransportSecurity</key>\n     \t<dict>\n     \t\t<key>NSAllowsArbitraryLoads</key>\n     \t\t<true/>\n     \t</dict>\n\n\n\n### ios release 打包失败配置注意事项\n\n<!--![](.README_images/ios.gif)-->\n<img src=\"README_images/ios_error.png\" width=\"400\"  align=center />\n\n\n\n \n## android配置注意事项\n \n### android 集成的的时候需要在application标签下添加 \n \n     \n      tools:replace=\"android:label\"\n     \n\n\n\n\n\n\n  \n## TODO\n\n* [ ] 双声道切换 单声道切换\n\n## 感谢\n\n\n[肖中旺](https://github.com/xzw421771880)对IOS 播放在线Wav的支持 \n\n\n## 作者的其他开源项目推荐\n\n\n[基于腾讯云点播封装的flutter版的播放器插件 ](https://github.com/yxwandroid/flutter_tencentplayer_plus)\n\n[Flutter 二维码扫描插件](https://github.com/yxwandroid/flutter_plugin_qrcode)\n\n[抖音开发平台SDK Flutter插件](https://github.com/yxwandroid/flutter_plugin_douyin_open)\n\n[FLutter地图插件](https://github.com/yxwandroid/flutter_amap_location)\n\n[Flutter 模板工程](https://github.com/yxwandroid/flutter_app_redux.git)\n\n## 关注公众号获取更多内容\n\n<img src=\"README_images/f53502b3.png\" width=\"200\" hegiht=\"313\" align=center />\n\n"
  },
  {
    "path": "android/.gitignore",
    "content": "*.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",
    "content": "group 'record.wilson.flutter.com.flutter_plugin_record'\nversion '1.0-SNAPSHOT'\n\nbuildscript {\n//    ext.kotlin_version = '1.2.71'\n    ext.kotlin_version = '1.3.50'\n\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.2.1'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\n\nrootProject.allprojects {\n    repositories {\n        google()\n        jcenter()\n        maven { url 'https://jitpack.io' }\n    }\n}\n\napply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\n\nandroid {\n    compileSdkVersion 29\n\n    sourceSets {\n        main.java.srcDirs += 'src/main/kotlin'\n    }\n    defaultConfig {\n        minSdkVersion 19\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n    lintOptions {\n        disable 'InvalidPackage'\n    }\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation 'com.github.shaoshuai904:RecordWav:1.0.2'\n    implementation \"androidx.appcompat:appcompat:1.0.0\"\n    implementation 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'\n}\n"
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.10.2-all.zip\n"
  },
  {
    "path": "android/gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx1536M\n\n"
  },
  {
    "path": "android/settings.gradle",
    "content": "rootProject.name = 'flutter_plugin_record'\n"
  },
  {
    "path": "android/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  package=\"record.wilson.flutter.com.flutter_plugin_record\">\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n</manifest>\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/FlutterPluginRecordPlugin.kt",
    "content": "package record.wilson.flutter.com.flutter_plugin_record\n\nimport android.Manifest\nimport android.app.Activity\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport cafe.adriel.androidaudioconverter.AndroidAudioConverter\nimport cafe.adriel.androidaudioconverter.callback.IConvertCallback\nimport cafe.adriel.androidaudioconverter.callback.ILoadCallback\nimport cafe.adriel.androidaudioconverter.model.AudioFormat\nimport io.flutter.embedding.engine.plugins.FlutterPlugin\nimport io.flutter.embedding.engine.plugins.activity.ActivityAware\nimport io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding\nimport io.flutter.plugin.common.BinaryMessenger\nimport io.flutter.plugin.common.MethodCall\nimport io.flutter.plugin.common.MethodChannel\nimport io.flutter.plugin.common.MethodChannel.MethodCallHandler\nimport io.flutter.plugin.common.MethodChannel.Result\nimport io.flutter.plugin.common.PluginRegistry\nimport io.flutter.plugin.common.PluginRegistry.Registrar\nimport record.wilson.flutter.com.flutter_plugin_record.utils.*\nimport java.io.File\nimport java.util.*\n\n\nclass FlutterPluginRecordPlugin : FlutterPlugin, MethodCallHandler, ActivityAware ,PluginRegistry.RequestPermissionsResultListener {\n\n    lateinit var channel: MethodChannel\n    private lateinit var _result: Result\n    private lateinit var call: MethodCall\n    private lateinit var voicePlayPath: String\n    private var recorderUtil: RecorderUtil? = null\n    private var recordMp3:Boolean=false;\n\n    @Volatile\n    private var audioHandler: AudioHandler? = null\n\n    lateinit var activity:Activity\n\n    companion object {\n        //support embedding v1\n        @JvmStatic\n        fun registerWith(registrar: Registrar) {\n            val plugin = initPlugin(registrar.messenger())\n            plugin.activity=registrar.activity()\n            registrar.addRequestPermissionsResultListener(plugin)\n        }\n\n        private fun initPlugin(binaryMessenger: BinaryMessenger):FlutterPluginRecordPlugin {\n            val channel = createMethodChannel(binaryMessenger)\n            val plugin = FlutterPluginRecordPlugin()\n            channel.setMethodCallHandler(plugin)\n            plugin.channel = channel\n            return plugin\n        }\n\n        private fun createMethodChannel(binaryMessenger: BinaryMessenger):MethodChannel{\n            return  MethodChannel(binaryMessenger, \"flutter_plugin_record\");\n        }\n    }\n    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {\n       val methodChannel = createMethodChannel(binding.binaryMessenger)\n        methodChannel.setMethodCallHandler(this)\n        channel=methodChannel\n    }\n\n\n\n    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {\n    }\n\n    override fun onAttachedToActivity(binding: ActivityPluginBinding) {\n        initActivityBinding(binding)\n    }\n\n    private fun initActivityBinding(binding: ActivityPluginBinding) {\n        binding.addRequestPermissionsResultListener(this)\n        activity=binding.activity\n    }\n\n\n    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {\n        initActivityBinding(binding)\n    }\n\n    override fun onDetachedFromActivityForConfigChanges() {\n    }\n    override fun onDetachedFromActivity() {\n    }\n\n\n    override  fun onMethodCall(call: MethodCall, result: Result) {\n        _result = result\n        this.call = call\n        when (call.method) {\n            \"init\" -> init()\n            \"initRecordMp3\" -> initRecordMp3()\n            \"start\" -> start()\n            \"startByWavPath\" -> startByWavPath()\n            \"stop\" -> stop()\n            \"play\" -> play()\n            \"pause\" -> pause()\n            \"playByPath\" -> playByPath()\n            \"stopPlay\" -> stopPlay()\n            else -> result.notImplemented()\n        }\n    }\n\n\n\n    //初始化wav转 MP3\n    private fun initWavToMp3(){\n        AndroidAudioConverter.load(activity.applicationContext, object : ILoadCallback {\n            override fun onSuccess() {\n                // Great!\n                Log.d(\"android\", \"  AndroidAudioConverter onSuccess\")\n            }\n\n            override fun onFailure(error: Exception) {\n                // FFmpeg is not supported by device\n                Log.d(\"android\", \"  AndroidAudioConverter onFailure\")\n            }\n        })\n\n    }\n\n    private fun initRecord() {\n        if (audioHandler != null) {\n            audioHandler?.release()\n            audioHandler = null\n        }\n        audioHandler = AudioHandler.createHandler(AudioHandler.Frequency.F_22050)\n\n        Log.d(\"android voice  \", \"init\")\n        val id = call.argument<String>(\"id\")\n        val m1 = HashMap<String, String>()\n        m1[\"id\"] = id!!\n        m1[\"result\"] = \"success\"\n        channel.invokeMethod(\"onInit\", m1)\n\n    }\n\n    private fun stopPlay() {\n        recorderUtil?.stopPlay()\n    }\n    //暂停播放\n    private fun pause() {\n        val isPlaying= recorderUtil?.pausePlay()\n        val _id = call.argument<String>(\"id\")\n        val m1 = HashMap<String, String>()\n        m1[\"id\"] = _id!!\n        m1[\"result\"] = \"success\"\n        m1[\"isPlaying\"] = isPlaying.toString()\n        channel.invokeMethod(\"pausePlay\", m1)\n    }\n\n    private fun play() {\n\n        recorderUtil = RecorderUtil(voicePlayPath)\n        recorderUtil!!.addPlayStateListener { playState ->\n            print(playState)\n            val _id = call.argument<String>(\"id\")\n            val m1 = HashMap<String, String>()\n            m1[\"id\"] = _id!!\n            m1[\"playPath\"] = voicePlayPath\n            m1[\"playState\"] = playState.toString()\n            channel.invokeMethod(\"onPlayState\", m1)\n        }\n        recorderUtil!!.playVoice()\n        Log.d(\"android voice  \", \"play\")\n        val _id = call.argument<String>(\"id\")\n        val m1 = HashMap<String, String>()\n        m1[\"id\"] = _id!!\n        channel.invokeMethod(\"onPlay\", m1)\n    }\n\n    private fun playByPath() {\n        val path = call.argument<String>(\"path\")\n        recorderUtil = RecorderUtil(path)\n        recorderUtil!!.addPlayStateListener { playState ->\n            val _id = call.argument<String>(\"id\")\n            val m1 = HashMap<String, String>()\n            m1[\"id\"] = _id!!\n            m1[\"playPath\"] = path.toString();\n            m1[\"playState\"] = playState.toString()\n            channel.invokeMethod(\"onPlayState\", m1)\n        }\n        recorderUtil!!.playVoice()\n\n        Log.d(\"android voice  \", \"play\")\n        val _id = call.argument<String>(\"id\")\n        val m1 = HashMap<String, String>()\n        m1[\"id\"] = _id!!\n        channel.invokeMethod(\"onPlay\", m1)\n    }\n\n    @Synchronized\n    private fun stop() {\n        if (audioHandler != null) {\n            if (audioHandler?.isRecording == true) {\n                audioHandler?.stopRecord()\n            }\n        }\n        Log.d(\"android voice  \", \"stop\")\n    }\n\n    @Synchronized\n    private fun start() {\n        var packageManager = activity.packageManager\n        var permission = PackageManager.PERMISSION_GRANTED == packageManager.checkPermission(Manifest.permission.RECORD_AUDIO,activity.packageName)\n        if (permission) {\n            Log.d(\"android voice  \", \"start\")\n            //        recorderUtil.startRecord();\n            if (audioHandler?.isRecording == true) {\n//            audioHandler?.startRecord(null);\n                audioHandler?.stopRecord()\n            }\n            audioHandler?.startRecord(MessageRecordListener())\n\n\n            val _id = call.argument<String>(\"id\")\n            val m1 = HashMap<String, String>()\n            m1[\"id\"] = _id!!\n            m1[\"result\"] = \"success\"\n            channel.invokeMethod(\"onStart\", m1)\n        } else {\n            checkPermission()\n        }\n\n    }\n\n    @Synchronized\n    private fun startByWavPath() {\n        var packageManager = activity.packageManager\n        var permission = PackageManager.PERMISSION_GRANTED == packageManager.checkPermission(Manifest.permission.RECORD_AUDIO, activity.packageName)\n        if (permission) {\n            Log.d(\"android voice  \", \"start\")\n            val _id = call.argument<String>(\"id\")\n            val wavPath = call.argument<String>(\"wavPath\")\n\n            if (audioHandler?.isRecording == true) {\n                audioHandler?.stopRecord()\n            }\n            audioHandler?.startRecord(wavPath?.let { MessageRecordListenerByPath(it) })\n\n\n            val m1 = HashMap<String, String>()\n            m1[\"id\"] = _id!!\n            m1[\"result\"] = \"success\"\n            channel.invokeMethod(\"onStart\", m1)\n        } else {\n            checkPermission()\n        }\n\n    }\n\n\n    private fun init() {\n        recordMp3=false\n        checkPermission()\n    }\n    private fun initRecordMp3(){\n        recordMp3=true\n        checkPermission()\n        initWavToMp3()\n    }\n\n    private fun checkPermission() {\n        var packageManager = activity.packageManager\n        var permission = PackageManager.PERMISSION_GRANTED == packageManager.checkPermission(Manifest.permission.RECORD_AUDIO,activity.packageName)\n        if (permission) {\n            initRecord()\n        } else {\n            initPermission()\n        }\n\n\n    }\n\n    private fun initPermission() {\n        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) !== PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)\n        }\n    }\n\n\n    //自定义路径\n    private inner class MessageRecordListenerByPath : AudioHandler.RecordListener {\n        var wavPath = \"\"\n\n        constructor(wavPath: String) {\n            this.wavPath = wavPath\n        }\n\n\n        override fun onStop(recordFile: File?, audioTime: Double?) {\n            if (recordFile != null) {\n                voicePlayPath = recordFile.path\n                if (recordMp3){\n\n                    val callback: IConvertCallback = object : IConvertCallback {\n                        override fun onSuccess(convertedFile: File) {\n\n                            Log.d(\"android\", \"  ConvertCallback ${convertedFile.path}\")\n\n                            val _id = call.argument<String>(\"id\")\n                            val m1 = HashMap<String, String>()\n                            m1[\"id\"] = _id!!\n                            m1[\"voicePath\"] = convertedFile.path\n                            m1[\"audioTimeLength\"] = audioTime.toString()\n                            m1[\"result\"] = \"success\"\n                            activity.runOnUiThread { channel.invokeMethod(\"onStop\", m1) }\n                        }\n\n                        override fun onFailure(error: java.lang.Exception) {\n                            Log.d(\"android\", \"  ConvertCallback $error\")\n                        }\n                    }\n                    AndroidAudioConverter.with(activity.applicationContext)\n                            .setFile(recordFile)\n                            .setFormat(AudioFormat.MP3)\n                            .setCallback(callback)\n                            .convert()\n\n                }else{\n                    val _id = call.argument<String>(\"id\")\n                    val m1 = HashMap<String, String>()\n                    m1[\"id\"] = _id!!\n                    m1[\"voicePath\"] = voicePlayPath\n                    m1[\"audioTimeLength\"] = audioTime.toString()\n                    m1[\"result\"] = \"success\"\n                    activity.runOnUiThread { channel.invokeMethod(\"onStop\", m1) }\n\n                }\n            }\n\n        }\n\n\n        override fun getFilePath(): String {\n            return wavPath;\n        }\n\n        private val fileName: String\n        private val cacheDirectory: File\n\n\n        init {\n            cacheDirectory = FileTool.getIndividualAudioCacheDirectory(activity)\n            fileName = UUID.randomUUID().toString()\n        }\n\n        override fun onStart() {\n            LogUtils.LOGE(\"MessageRecordListener onStart on start record\")\n        }\n\n        override fun onVolume(db: Double) {\n            LogUtils.LOGE(\"MessageRecordListener onVolume \" + db / 100)\n            val _id = call.argument<String>(\"id\")\n            val m1 = HashMap<String, Any>()\n            m1[\"id\"] = _id!!\n            m1[\"amplitude\"] = db / 100\n            m1[\"result\"] = \"success\"\n\n            activity.runOnUiThread { channel.invokeMethod(\"onAmplitude\", m1) }\n\n\n        }\n\n        override fun onError(error: Int) {\n            LogUtils.LOGE(\"MessageRecordListener onError $error\")\n        }\n    }\n\n\n    private inner class MessageRecordListener : AudioHandler.RecordListener {\n        override fun onStop(recordFile: File?, audioTime: Double?) {\n            LogUtils.LOGE(\"MessageRecordListener onStop $recordFile\")\n            if (recordFile != null) {\n                voicePlayPath = recordFile.path\n                if (recordMp3){\n                    val callback: IConvertCallback = object : IConvertCallback {\n                        override fun onSuccess(convertedFile: File) {\n\n                            Log.d(\"android\", \"  ConvertCallback ${convertedFile.path}\")\n\n                            val _id = call.argument<String>(\"id\")\n                            val m1 = HashMap<String, String>()\n                            m1[\"id\"] = _id!!\n                            m1[\"voicePath\"] = convertedFile.path\n                            m1[\"audioTimeLength\"] = audioTime.toString()\n                            m1[\"result\"] = \"success\"\n                            activity.runOnUiThread { channel.invokeMethod(\"onStop\", m1) }\n                        }\n\n                        override fun onFailure(error: java.lang.Exception) {\n                            Log.d(\"android\", \"  ConvertCallback $error\")\n                        }\n                    }\n                    AndroidAudioConverter.with(activity.applicationContext)\n                            .setFile(recordFile)\n                            .setFormat(AudioFormat.MP3)\n                            .setCallback(callback)\n                            .convert()\n\n                }else{\n                    val _id = call.argument<String>(\"id\")\n                    val m1 = HashMap<String, String>()\n                    m1[\"id\"] = _id!!\n                    m1[\"voicePath\"] = voicePlayPath\n                    m1[\"audioTimeLength\"] = audioTime.toString()\n                    m1[\"result\"] = \"success\"\n                    activity.runOnUiThread { channel.invokeMethod(\"onStop\", m1) }\n\n                }\n            }\n\n        }\n\n\n        override fun getFilePath(): String {\n            val file = File(cacheDirectory, fileName)\n            return file.absolutePath\n        }\n\n        private val fileName: String\n        private val cacheDirectory: File\n\n\n        init {\n            cacheDirectory = FileTool.getIndividualAudioCacheDirectory(activity)\n            fileName = UUID.randomUUID().toString()\n        }\n\n        override fun onStart() {\n            LogUtils.LOGE(\"MessageRecordListener onStart on start record\")\n        }\n\n        override fun onVolume(db: Double) {\n            LogUtils.LOGE(\"MessageRecordListener onVolume \" + db / 100)\n            val _id = call.argument<String>(\"id\")\n            val m1 = HashMap<String, Any>()\n            m1[\"id\"] = _id!!\n            m1[\"amplitude\"] = db / 100\n            m1[\"result\"] = \"success\"\n\n            activity.runOnUiThread { channel.invokeMethod(\"onAmplitude\", m1) }\n\n\n        }\n\n        override fun onError(error: Int) {\n            LogUtils.LOGE(\"MessageRecordListener onError $error\")\n        }\n    }\n\n\n\n\n    // 权限监听回调\n    override fun onRequestPermissionsResult(p0: Int, p1: Array<out String>?, p2: IntArray?): Boolean {\n        if (p0 == 1) {\n            if (p2?.get(0) == PackageManager.PERMISSION_GRANTED) {\n//                initRecord()\n                return true\n            } else {\n\n                Toast.makeText(activity, \"Permission Denied\", Toast.LENGTH_SHORT).show()\n                DialogUtil.Dialog(activity, \"申请权限\")\n            }\n            return false\n        }\n\n        return false\n    }\n\n    \n\n}\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimer.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\n\n\npublic interface ITimer {\n    void startTimer();\n\n    void pauseTimer();\n\n    void resumeTimer();\n\n    void stopTimer();\n}\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimerChangeCallback.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\npublic interface ITimerChangeCallback {\n    void onTimeChange(long time);\n}"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/MTimer.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\nimport android.text.TextUtils;\n\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class MTimer implements ITimer {\n    //--real timer--\n    private Timer timer;\n    //--default task--\n    private TimerTask task;\n    //--default initdelay--\n    private long initDelay = 0l;\n    //--default delay--\n    private long delay = 0l;\n    //--call back--\n    private ITimerChangeCallback[] callbacks = null;\n    //--real time--\n    private AtomicLong time;//时间的记录工具\n\n    //--action--\n    private static final int START = 0;\n    private static final int PAUSE = 1;\n    private static final int RESUME = 2;\n    private static final int STOP = 3;\n\n    private int status = STOP;//默认是stop\n\n    private MTimer(long initDelay, long delay, ITimerChangeCallback[] callbacks) {\n        this.initDelay = initDelay;\n        this.delay = delay;\n        this.callbacks = callbacks;\n    }\n\n    //-----------------外部方法------------------------\n\n    /**\n     * 用于生成MTimer 对象\n     *\n     * @return --MTimer\n     */\n    public static Builder makeTimerBuilder() {\n        return new Builder();\n    }\n\n    /**\n     * 开启 timer\n     */\n    @Override\n    public void startTimer() {\n        //判断当前是不是stop,是的话开始运行\n        if (status != STOP) {\n            return;\n        }\n        //切换当前状态为 start\n        status = START;\n        realStartTimer(true);\n    }\n\n    /**\n     * 暂停timer\n     */\n    @Override\n    public void pauseTimer() {\n        //判断当前是不是start 是不是resume,如果是其中一个就可以\n        if (status != START && status != RESUME) {\n            return;\n        }\n        //切换当前状态为 pause\n        status = PAUSE;\n        realStopTimer(false);\n    }\n\n    /**\n     * 重启timer\n     */\n    @Override\n    public void resumeTimer() {\n        //判断当前是不是pause ,如果是则恢复\n        if (status != PAUSE) {\n            return;\n        }\n        //切换当前状态为 resume\n        status = RESUME;\n        realStartTimer(false);\n    }\n\n    /**\n     * 关闭timer\n     */\n    @Override\n    public void stopTimer() {\n        //无论当前处于那种状态都可以stop\n        status = STOP;\n        realStopTimer(true);\n    }\n\n    //-----------------内部方法------------------------\n\n    /**\n     * timer 真正的开始方法\n     *\n     * @param isToZero --是否清除数据\n     */\n    private void realStartTimer(boolean isToZero) {\n        //清空记录时间\n        if (isToZero) {\n            time = new AtomicLong(0);\n        }\n        //重新生成timer、task\n        if (timer == null && task == null) {\n            timer = new Timer();\n            task = createTask();\n            timer.scheduleAtFixedRate(task, initDelay, delay);\n        }\n    }\n\n    /**\n     * timer 真正的关闭方法\n     *\n     * @param isToZero --是否清除数据\n     */\n    private void realStopTimer(boolean isToZero) {\n        //清空记录时间\n        if (isToZero) {\n            time = new AtomicLong(0);\n        }\n        //关闭当前的timer\n        if (timer != null) {\n            timer.purge();\n            timer.cancel();\n            timer = null;\n\n        }\n        //关闭当前任务\n        if (task != null) {\n            task.cancel();\n            task = null;\n        }\n    }\n\n\n    /**\n     * 判断是否设置监听回调\n     *\n     * @return -- true 表示设置了回调，反之表示没设置\n     */\n    private boolean checkCallback() {\n        return callbacks != null && callbacks.length > 0;\n    }\n\n    /**\n     * 创建task\n     *\n     * @return\n     */\n    private TimerTask createTask() {\n        TimerTask task = new TimerTask() {\n            @Override\n            public void run() {\n                time.incrementAndGet();\n                notifyCallback(time);\n            }\n        };\n        return task;\n    }\n\n    /**\n     * 通知callback\n     *\n     * @param time --间距走的次数（花费时间=次数*delay+initDelay）\n     */\n    private void notifyCallback(AtomicLong time) {\n        if (checkCallback()) {\n            for (ITimerChangeCallback callback : callbacks) {\n                callback.onTimeChange(time.longValue());\n            }\n        }\n    }\n\n    public static class Builder {\n\n        //--default initdelay--\n        private long initDelay = 0l;\n        //--default delay--\n        private long delay = 0l;\n        //--call back--\n        private ITimerChangeCallback[] callbacks = null;\n        //--tag--\n        private String tag;\n\n        public Builder setTag(String tag) {\n            if (TextUtils.isEmpty(tag)) {\n                throw new NullPointerException(\"设置的tag无效！=>setTag(String tag)\");\n            }\n            this.tag = tag;\n            return this;\n        }\n\n\n        /**\n         * 设置执行当前任务的时候首次执行时的延迟时间\n         *\n         * @param initDelay --首次执行的延迟时间(ms)\n         */\n        public Builder setInitDelay(long initDelay) {\n            this.initDelay = initDelay;\n            return this;\n        }\n\n        /**\n         * 设置时间回调\n         *\n         * @param callbacks\n         */\n        public Builder setCallbacks(ITimerChangeCallback... callbacks) {\n            this.callbacks = callbacks;\n            return this;\n        }\n\n        /**\n         * 设置后续的延迟时间\n         *\n         * @param delay --后续延迟时间(ms)\n         */\n        public Builder setDelay(long delay) {\n            this.delay = delay;\n            return this;\n        }\n\n        /**\n         * 外部会重用此对象，所以需要重置其参数\n         */\n        public void reset() {\n            tag = null;\n            initDelay = 0l;\n            delay = 0l;\n            callbacks = null;\n        }\n\n        /**\n         * 最终的生成方法，如果不调用此处，timer无法运行\n         */\n        public MTimer build() {\n            //--check delay--\n            if (initDelay < 0 || delay < 0) {\n                throw new AssertionError(\"initDelay或delay 不允许小于0\");\n            }\n            //--build timer--\n            MTimer timer = new MTimer(initDelay, delay, callbacks);\n            //--add to cache--\n            if (!TextUtils.isEmpty(tag)) {\n                TimerUtils.addTimerToCache(tag, timer);\n            }\n            //--return timer--\n            return timer;\n        }\n\n    }\n}"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/TimerUtils.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.timer;\n\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.util.Locale;\nimport java.util.WeakHashMap;\n/**\n * description: 定时器工具类\n * author: Simon\n * created at 2017/8/10 上午9:47\n */\n\npublic final class TimerUtils {\n    //--tag--\n    private static final String TAG = \"TimerUtils\";\n    //--err info--\n    private static final String ERR_INFO = \"未找到对应的MTimer,确认是否设置过Tag!=>new Builder().setTag(String tag)\";\n    //--cache--\n    private static WeakHashMap<String, MTimer> cacheTimerMap = new WeakHashMap<>();\n    //--action--\n    private static final int START = 0;\n    private static final int PAUSE = 1;\n    private static final int RESUME = 2;\n    private static final int STOP = 3;\n\n    //--recycle build--\n    private static final MTimer.Builder BUILDER = new MTimer.Builder();\n\n    private TimerUtils() {\n        throw new AssertionError(\"you can't init me！\");\n    }\n\n    /**\n     * 注意此方法，会重复利用Builder 对象，所以每次build()完成后再重新使用该方法！！\n     *\n     * @return --builder\n     */\n    public static MTimer.Builder makeBuilder() {\n        BUILDER.reset();//每次执行的时候都会重置一次\n        return BUILDER;\n    }\n\n    /**\n     * 开启timer ，时间清零\n     */\n    public static void startTimer(String tag) {\n        actionTimer(START, tag);\n    }\n\n    /**\n     * 恢复timer，不清零\n     */\n    public static void resumeTimer(String tag) {\n        actionTimer(RESUME, tag);\n    }\n\n    /**\n     * 暂停timer\n     */\n    public static void pauseTimer(String tag) {\n        actionTimer(PAUSE, tag);\n    }\n\n    /**\n     * 关闭 timer\n     */\n    public static void stopTimer(String tag) {\n        actionTimer(STOP, tag);\n    }\n\n    /**\n     * 格式化 时间 格式为  hh:mm:ss\n     *\n     * @param cnt\n     * @return\n     */\n    public static String formatTime(long cnt) {\n        long hour = cnt / 3600;\n        long min = cnt % 3600 / 60;\n        long second = cnt % 60;\n        return String.format(Locale.CHINA, \"%02d:%02d:%02d\", hour, min, second);\n    }\n\n    //------------------------私有方法/内部类------------------------------\n\n    /**\n     * 添加timer到缓存\n     *\n     * @param tag   --tag\n     * @param timer --timer\n     */\n    public static void addTimerToCache(String tag, MTimer timer) {\n        if (cacheTimerMap == null) {\n            cacheTimerMap = new WeakHashMap<>();\n        }\n        cacheTimerMap.put(tag, timer);\n    }\n\n    /**\n     * 真正的执行方法\n     *\n     * @param action --行为\n     * @param tag    --tag\n     */\n    private static void actionTimer(int action, String tag) {\n        //-----check tag----\n        if (!checkTag(tag)) {\n            Log.e(TAG, \"The tag is empty or null！\");\n            return;\n        }\n        //-----check timer----\n        MTimer timer = findMTimerByTag(tag);\n        if (timer == null) {\n            Log.e(TAG, \"Can't found timer by tag!\");\n            return;\n        }\n\n        //-----action timer----\n        switch (action) {\n            case START:\n                timer.startTimer();\n                break;\n            case RESUME:\n                timer.resumeTimer();\n                break;\n            case PAUSE:\n                timer.pauseTimer();\n                break;\n            case STOP:\n                timer.stopTimer();\n                break;\n        }\n\n\n    }\n\n\n    /**\n     * 通过tag获取mtimer\n     *\n     * @param tag --设置的tag\n     * @return --MTimer\n     */\n    private static MTimer findMTimerByTag(String tag) {\n        if (!checkTag(tag) || cacheTimerMap == null || cacheTimerMap.size() == 0) {//tag无效，没有缓存数据，返回null\n            return null;\n        } else {//反之根据tag返回\n            return cacheTimerMap.get(tag);\n        }\n    }\n\n    /**\n     * 判断tag 是否有效\n     *\n     * @param tag --tag\n     * @return true表示有效，反之无效\n     */\n    private static boolean checkTag(String tag) {\n        return !TextUtils.isEmpty(tag);\n    }\n\n\n}\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/AudioHandler.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.Process;\nimport android.util.Log;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.RandomAccessFile;\nimport java.lang.ref.WeakReference;\nimport java.text.SimpleDateFormat;\nimport java.util.Locale;\n\nimport record.wilson.flutter.com.flutter_plugin_record.timer.ITimerChangeCallback;\nimport record.wilson.flutter.com.flutter_plugin_record.timer.TimerUtils;\npublic final class AudioHandler extends Handler {\n\n    private static final String TAG = \"AudioHandler\";\n\n    private final        WeakReference<AudioThread> mThread;\n    private static final int                        MESSAGE_START_RECORD      = 0X01;\n    private static final int                        MESSAGE_PAUSE_RECORD      = 0X02;\n    private static final int                        MESSAGE_STOP_RECORD       = 0X03;\n    private static final int                        MESSAGE_SAVE_RECORD       = 0X04;\n    private static final int                        MESSAGE_ADD_LISTENER      = 0X05;\n    private static final int                        MESSAGE_REMOVE_LISTENER   = 0X06;\n    private static final int                        MESSAGE_RELEASE           = 0X07;\n    private static final int                        MESSAGE_GET_LATEST_RECORD = 0X08;\n\n    public static final int MAX_DB = 96;\n\n    public static final int STATE_AUDIO_RECORD_PREPARING = 0X01;\n    public static final int STATE_AUDIO_RECORD_START     = 0X02;\n    public static final int STATE_AUDIO_RECORD_PAUSE     = 0X03;\n    public static final int STATE_AUDIO_RECORD_STOPPED   = 0X04;\n\n    public static AudioHandler createHandler(Frequency frequency) {\n        AudioThread thread = new AudioThread(frequency);\n        thread.start();\n        return thread.getHandler();\n    }\n\n    private AudioHandler(AudioThread thread) {\n        mThread = new WeakReference<AudioThread>(thread);\n    }\n\n    public void startRecord(RecordListener listener) {\n        if (isRecording()) stopRecord();\n        Message message = obtainMessage(MESSAGE_START_RECORD);\n        message.obj = listener;\n        sendMessage(message);\n    }\n\n    public boolean isAvailable() {\n        AudioThread audioThread = mThread.get();\n        return audioThread != null && audioThread.isAvailable;\n    }\n\n    public void stopRecord() {\n        AudioThread audioThread = mThread.get();\n        if (audioThread != null) audioThread.setPauseRecord();\n    }\n\n    public void cancelRecord() {\n        AudioThread audioThread = mThread.get();\n        if (audioThread != null) audioThread.setCancelRecord();\n    }\n\n    public boolean isRecording() {\n        AudioThread audioThread = mThread.get();\n        return audioThread != null && audioThread.isRecording();\n    }\n\n    public void release() {\n        AudioThread audioThread = mThread.get();\n        if (audioThread != null) audioThread.release();\n    }\n\n    @Override\n    public void handleMessage(Message msg) {\n        super.handleMessage(msg);\n        AudioThread audioThread = mThread.get();\n        if (audioThread == null) return;\n        switch (msg.what) {\n            case MESSAGE_START_RECORD:\n                Object obj = msg.obj;\n                RecordListener listener = null;\n                if (obj instanceof RecordListener)\n                    listener = (RecordListener) obj;\n                audioThread.startRecord(listener);\n                break;\n            default:\n                break;\n        }\n    }\n\n    public enum Frequency {\n        F_44100(44100),\n        F_22050(22050),\n        F_16000(16000),\n        F_11025(11025),\n        F_8000(8000);\n        private int f;\n\n        private Frequency(int f) {\n            this.f = f;\n        }\n\n        public int getFrequency() {\n            return f;\n        }\n\n    }\n\n    private static final class AudioThread extends Thread {\n\n        private static final int[] FREQUENCY = {\n                44100,\n                22050,\n                16000,\n                11025,\n                8000\n        };\n        private final        int   mPriority;\n\n        private       AudioHandler     mHandler;\n        private       boolean          isAvailable;\n        private final Object           sync        = new Object();\n        private       int              mFrequency;\n        private       int              channel     = AudioFormat.CHANNEL_IN_MONO;\n        private       int              audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n        private       int              bufferSize;\n        private       AudioRecord      mRecord;\n        private       SimpleDateFormat format;\n        private       int              mTid;\n        private       Looper           mLooper;\n\n        private volatile boolean isCancel;\n        private String tag = \"AudioTimerTag\";\n\n        private double audioTime = 0;  //录音时长\n\n        //--设置 tag 后可以通过 tag 操作--\n        private void initTimer() {\n            TimerUtils.makeBuilder().setTag(tag).setInitDelay(0).setDelay(100).setCallbacks(new ITimerChangeCallback() {\n                @Override\n                public void onTimeChange(long time) {\n                    //Log.v(\"AudioTimerTag\", time + \"--> AudioTimer\");\n                    audioTime = time / 10.0;\n                }\n            }).build();\n\n        }\n\n        private AudioThread(Frequency frequency) {\n            mPriority = Process.THREAD_PRIORITY_DEFAULT;\n            mFrequency = frequency.getFrequency();\n            isAvailable = checkSampleRatesValid(mFrequency);\n            Log.e(TAG, \"FREQUENCY \" + mFrequency);\n            format = new SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\", Locale.getDefault());\n            if (isAvailable) {\n                bufferSize = AudioRecord.getMinBufferSize(mFrequency, channel, audioFormat);\n                Log.e(TAG, String.format(\"buffer size %d\", bufferSize));\n            }\n            initTimer();\n        }\n\n        boolean isRecording() {\n            if (mRecord == null) {\n                return false;\n            }\n            if (mRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {\n                return false;\n            }\n            return mRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;\n        }\n\n        private volatile boolean isRecording;\n\n        private void startRecord(RecordListener listener) {\n\n            audioTime =0;\n            TimerUtils.startTimer(tag);\n            Log.e(TAG, \"call start record\");\n            if (!isAvailable) {\n                return;\n            }\n            byte[] buffer = new byte[bufferSize];\n            pauseRecord();\n            if (mRecord == null) {\n                mRecord = new AudioRecord(MediaRecorder.AudioSource.MIC\n                        , mFrequency\n                        , channel\n                        , audioFormat\n                        , bufferSize * 10);\n            }\n\n            if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) {\n                pauseRecord();\n                return;\n            }\n\n            BufferedOutputStream out = null;\n            File recordFile = null;\n            if (listener != null) {\n                String filePath = listener.getFilePath();\n                if (!filePath.endsWith(\".wav\"))\n                    filePath = filePath + \".wav\";\n                recordFile = new File(filePath);\n            }\n            try {\n                if (listener != null) {\n                    if (!recordFile.createNewFile()) {\n                        listener.onError(-20);\n                        pauseRecord();\n                        return;\n                    }\n                    out = new BufferedOutputStream(new FileOutputStream(recordFile));\n                    WaveHeaderHelper.writeHeader(out, mFrequency, 16, 1);\n                }\n                mRecord.startRecording();\n                isCancel = false;\n                if (listener != null) listener.onStart();\n                Log.e(TAG, \"start recording\");\n                isRecording = true;\n//                Log.d(TAG, \"BUFFER LIMIT IS \" + buffer.limit() + \"\\n\\t\\t\\tCAPACITY IS\" + buffer.capacity());\n                long length = 0;\n                boolean turn = false;\n                while (isRecording && mRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n                    int read = mRecord.read(buffer, 0, buffer.length);\n                    if (read < 0) {\n                        Log.e(TAG, read == -3 ? \"ERROR_INVALID_OPERATION\"\n                                : read == -2 ? \"ERROR_BAD_VALUE\"\n                                : read == -1 ? \"ERROR\"\n                                : String.valueOf(read));\n                        if (listener != null) listener.onError(read);\n                        break;\n                    }\n                    if (out != null) {\n                        out.write(buffer, 0, read);\n                    }\n                    if (listener != null) {\n                        if (turn) {\n                            listener.onVolume(getDb(buffer));\n                        }\n                        turn = !turn;\n                    }\n                    length += read;\n                }\n\n            } catch (Throwable t) {\n                t.printStackTrace();\n            } finally {\n                pauseRecord();\n                FileTool.closeIO(out);\n            }\n            if (listener != null) {\n                WaveHeaderHelper.writeWaveHeaderLength(recordFile);\n                if (isCancel) {\n                    recordFile.deleteOnExit();\n                    recordFile = null;\n                }\n                TimerUtils.stopTimer(tag);\n//                listener.onStop(recordFile);\n                listener.onStop(recordFile, audioTime);\n            }\n        }\n\n        public void setPauseRecord() {\n            isRecording = false;\n        }\n\n        public void setCancelRecord() {\n            isCancel = true;\n            setPauseRecord();\n        }\n\n        private double getDb(byte[] buffer) {\n            double diviation = getDiviation(buffer, 0, buffer.length);\n            return 20 * Math.log10(diviation);\n\n        }\n\n        private static short getShort(byte argB1, byte argB2) {\n            //if (ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) {\n            //    return (short) ((argB1 << 8) | argB2);\n            //}\n            return (short) (argB1 | (argB2 << 8));\n        }\n\n        private static double getDiviation(byte[] buffer, int start, int length) {\n            if (0 != (length % 2)) {\n                length--;\n            }\n            double[] divArray = new double[length];\n//        short[] array = ByteBuffer.wrap(buffer, start, length).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().array();\n            for (int i = start; i < start + length; i += 2) {\n                divArray[i / 2] = getShort(buffer[i], buffer[i + 1]) * 1.0;\n            }\n            return StandardDiviation(divArray);\n        }\n\n        //标准差σ=sqrt(s^2)\n        private static double StandardDiviation(double[] x) {\n            int m = x.length;\n            double sum = 0;\n            for (int i = 0; i < m; i++) {//求和\n                sum += x[i];\n            }\n            double dAve = sum / m;//求平均值\n            double dVar = 0;\n            for (int i = 0; i < m; i++) {//求方差\n                double v = x[i] - dAve;\n                dVar += v * v;\n            }\n            return Math.sqrt(dVar / m);\n        }\n\n        private void pauseRecord() {\n            setPauseRecord();\n            if (mRecord != null) {\n                if (mRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {\n                    mRecord.release();\n                    mRecord = null;\n                }\n                if (mRecord != null && mRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {\n                    mRecord.stop();\n                    mRecord.release();\n                    mRecord = null;\n                }\n            }\n        }\n\n        public void release() {\n            pauseRecord();\n            synchronized (sync) {\n                if (Looper.myLooper() != Looper.getMainLooper()) {\n                    if (mLooper != null) {\n                        mLooper.quit();\n                    }\n                }\n            }\n        }\n\n        @Override\n        public void run() {\n            mTid = Process.myTid();\n            Log.e(TAG, \"thread start running\");\n            Looper.prepare();\n            synchronized (sync) {\n                mLooper = Looper.myLooper();\n                mHandler = new AudioHandler(this);\n                sync.notifyAll();\n            }\n            Process.setThreadPriority(mPriority);\n            Looper.loop();\n            synchronized (sync) {\n                mHandler = null;\n                sync.notifyAll();\n            }\n            mTid = -1;\n        }\n\n        AudioHandler getHandler() {\n            if (!isAlive()) {\n                return null;\n            }\n            synchronized (sync) {\n                while (isAlive() && mHandler == null) {\n                    try {\n                        sync.wait();\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n            return mHandler;\n        }\n\n        public boolean checkSampleRatesValid(int frequency) {\n            int bufferSize = AudioRecord.getMinBufferSize(frequency,\n                    AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);\n            return bufferSize > 0;\n        }\n\n        public int getValidSampleRates() {\n            for (int i = 0; i < FREQUENCY.length; i++) {\n                int bufferSize = AudioRecord.getMinBufferSize(FREQUENCY[i],\n                        AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);\n                if (bufferSize > 0) {\n                    return FREQUENCY[i];\n                }\n            }\n            return -1;\n        }\n\n    }\n\n    public interface RecordListener {\n        void onStart();\n\n        String getFilePath();\n\n        void onVolume(double db);\n\n//        void onStop(File recordFile);\n        void onStop(File recordFile,Double audioTime);\n        void onError(int error);\n    }\n\n\n    private static class WaveHeaderHelper {\n\n        private static void writeHeader(OutputStream out, int sampleRate, int encoding, int channel) throws IOException {\n            writeString(out, \"RIFF\"); // chunk id\n            writeInt(out, 0); // chunk size\n            writeString(out, \"WAVE\"); // format\n            writeString(out, \"fmt \"); // subchunk 1 id\n            writeInt(out, 16); // subchunk 1 size\n            writeShort(out, (short) 1); // audio format (1 = PCM)\n            writeShort(out, (short) channel); // number of channels\n            writeInt(out, sampleRate); // sample rate\n            writeInt(out, sampleRate * channel * encoding >> 3); // byte rate\n            writeShort(out, (short) (channel * encoding >> 3)); // block align\n            writeShort(out, (short) encoding); // bits per sample\n            writeString(out, \"data\"); // subchunk 2 id\n            writeInt(out, 0); // subchunk 2 size\n        }\n\n        private static void writeWaveHeaderLength(File f) {\n            RandomAccessFile raf = null;\n            try {\n                raf = new RandomAccessFile(f, \"rw\");\n                long length = f.length();\n                long chunkSize = length - 8;\n                long subChunkSize = length - 44;\n                raf.seek(4);\n                raf.write((int) (chunkSize >> 0));\n                raf.write((int) (chunkSize >> 8));\n                raf.write((int) (chunkSize >> 16));\n                raf.write((int) (chunkSize >> 24));\n                raf.seek(40);\n                raf.write((int) (subChunkSize >> 0));\n                raf.write((int) (subChunkSize >> 8));\n                raf.write((int) (subChunkSize >> 16));\n                raf.write((int) (subChunkSize >> 24));\n            } catch (FileNotFoundException e) {\n                e.printStackTrace();\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                FileTool.closeIO(raf);\n            }\n        }\n\n        private static void writeInt(final OutputStream output, final int value) throws IOException {\n            output.write(value >> 0);\n            output.write(value >> 8);\n            output.write(value >> 16);\n            output.write(value >> 24);\n        }\n\n        private static void writeShort(final OutputStream output, final short value) throws IOException {\n            output.write(value >> 0);\n            output.write(value >> 8);\n        }\n\n        private static void writeString(final OutputStream output, final String value) throws IOException {\n            for (int i = 0; i < value.length(); i++) {\n                output.write(value.charAt(i));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DateUtils.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic class DateUtils {\n    public static String dateToString(Date date) {\n        String str = \"yyyyMMddhhmmss\";\n        SimpleDateFormat format = new SimpleDateFormat(str);\n        String dateFormat = format.format(date);\n        return dateFormat;\n    }\n}\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DialogUtil.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.app.AlertDialog;\n/**\n * 对话框管理\n * 打开app应用程序信息界面\n */\npublic class DialogUtil {\n\n    public static void Dialog(final Activity activity, String content) {\n         Dialog deleteDialog = new AlertDialog.Builder(activity)\n                .setTitle(\"提示\")\n                .setMessage(\"请进入应用信息界面开启录音权限\")\n                .setPositiveButton(\"确定\",\n                        new DialogInterface.OnClickListener() {\n                            public void onClick(DialogInterface dialog, int which) {\n                                startSetting(activity);\n                            }\n                        })\n                .create();\n        deleteDialog.setCanceledOnTouchOutside(false);\n        deleteDialog.setCancelable(false);\n        deleteDialog.show();\n    }\n\n\n    /**\n     * 启动app设置应用程序信息界面\n     */\n    public static void startSetting(Context context) {\n        Intent intent = new Intent();\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n//        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\n        if (Build.VERSION.SDK_INT >= 9) {\n            intent.setAction(\"android.settings.APPLICATION_DETAILS_SETTINGS\");\n            intent.setData(Uri.fromParts(\"package\", context.getPackageName(), null));\n        } else if (Build.VERSION.SDK_INT <= 8) {\n            intent.setAction(Intent.ACTION_VIEW);\n            intent.setClassName(\"com.android.settings\", \"com.android.settings.InstalledAppDetails\");\n            intent.putExtra(context.getPackageName(), context.getPackageName());\n        }\n        context.startActivity(intent);\n    }\n}\n\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/FileTool.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\r\n\r\nimport android.annotation.SuppressLint;\r\nimport android.content.Context;\r\nimport android.content.pm.PackageManager;\r\nimport android.media.ExifInterface;\r\nimport android.os.Build;\r\nimport android.os.Environment;\r\nimport android.os.StatFs;\r\nimport android.text.TextUtils;\r\nimport android.webkit.MimeTypeMap;\r\n\r\nimport java.io.BufferedInputStream;\r\nimport java.io.ByteArrayOutputStream;\r\nimport java.io.Closeable;\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.io.IOException;\r\nimport java.io.PrintStream;\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.net.FileNameMap;\r\nimport java.net.URLConnection;\r\nimport java.security.MessageDigest;\r\nimport java.security.NoSuchAlgorithmException;\r\nimport java.util.regex.Matcher;\r\nimport java.util.regex.Pattern;\r\n\r\nimport static android.os.Environment.MEDIA_MOUNTED;\r\n\r\npublic final class FileTool {\r\n\r\n    private FileTool() {\r\n    }\r\n\r\n    public static final String FILE_TYPE_PDF = \"pdf\";\r\n\r\n    public static final String FILE_TYPE_APK = \"vnd.android.package-archive\";\r\n\r\n    public static final String FILE_TYPE_EXCEL  = \"ms-excel\";\r\n    public static final String FILE_TYPE_EXCELX = \"vnd.openxmlformats-officedocument.spreadsheetml\";\r\n\r\n    public static final String FILE_TYPE_PPT  = \"powerpoint\";\r\n    public static final String FILE_TYPE_PPTX = \"vnd.openxmlformats-officedocument.presentationml\";\r\n\r\n    public static final String FILE_TYPE_WORD  = \"word\";\r\n    public static final String FILE_TYPE_WORDX = \"vnd.openxmlformats-officedocument.wordprocessingml\";\r\n\r\n    public static final String FILE_TYPE_RAR   = \"rar\";\r\n    public static final String FILE_TYPE_ZIP   = \"zip\";\r\n    public static final String FILE_TYPE_AUDIO = \"audio\";\r\n    public static final String FILE_TYPE_TEXT  = \"text\";\r\n    public static final String FILE_TYPE_XML   = \"xml\";\r\n    public static final String FILE_TYPE_HTML  = \"html\";\r\n    public static final String FILE_TYPE_IMAGE = \"image\";\r\n    public static final String FILE_TYPE_VIDEO = \"video\";\r\n    public static final String FILE_TYPE_APP   = \"application\";\r\n\r\n    /**\r\n     * @Fields maxFileSize : 最大允许文件大小\r\n     **/\r\n    private static final int MAX_FILE_SIZE = 2 * 1024 * 1024;\r\n\r\n    public static byte[] GetFileDataBytes(File file, int fileLen) {\r\n        ByteArrayOutputStream bos = null;\r\n        BufferedInputStream in = null;\r\n        try {\r\n            bos = new ByteArrayOutputStream(fileLen);\r\n            in = new BufferedInputStream(new FileInputStream(file));\r\n            int buf_size = 1024;\r\n            byte[] buffer = new byte[buf_size];\r\n            int len = 0;\r\n            while (-1 != (len = in.read(buffer, 0, buf_size))) {\r\n                bos.write(buffer, 0, len);\r\n            }\r\n            return bos.toByteArray();\r\n        } catch (Exception e) {\r\n            e.printStackTrace();\r\n        } finally {\r\n            closeIO(bos, in);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static String getFileRootPathString(Context context) {\r\n        return new String(getFileRootPath(context).getAbsolutePath());\r\n\r\n    }\r\n\r\n    public static String getDefaultApkSavePath() {\r\n        return new StringBuffer(Environment.getExternalStorageDirectory().getAbsolutePath()).append(File.separator).append(Environment.DIRECTORY_DOWNLOADS).toString();\r\n    }\r\n\r\n\r\n    public static File getFileRootPath(Context context) {\r\n        File file = null;\r\n        if (context == null) return null;\r\n        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {\r\n            file = context.getExternalCacheDir();\r\n        }\r\n        if (file == null) {\r\n            file = context.getCacheDir();\r\n        }\r\n        return file;\r\n    }\r\n\r\n//\tpublic static String getHeadImagePathString(Context mContext) {\r\n//\t\treturn getHeadImagePath(mContext).getAbsolutePath();\r\n//\t}\r\n\r\n    public static void copyFile2(String oldFilePath, String newFilePath) {\r\n        FileOutputStream fs = null;\r\n        FileInputStream inStream = null;\r\n        try {\r\n            fs = new FileOutputStream(newFilePath);\r\n            inStream = new FileInputStream(oldFilePath);\r\n            int byteread = 0;\r\n            if (inStream.available() <= MAX_FILE_SIZE) {\r\n                byte[] buffer = new byte[1024];\r\n                while ((byteread = inStream.read(buffer)) != -1) {\r\n                    fs.write(buffer, 0, byteread);\r\n                }\r\n            }\r\n        } catch (Exception e) {\r\n            e.printStackTrace();\r\n        } finally {\r\n            closeIO(fs, inStream);\r\n        }\r\n    }\r\n\r\n    public static boolean isValidName(String text) {\r\n        Pattern pattern = Pattern.compile(\r\n                \"# Match a valid Windows filename (unspecified file system).          \\n\" +\r\n                        \"^                                # Anchor to start of string.        \\n\" +\r\n                        \"(?!                              # Assert filename is not: CON, PRN, \\n\" +\r\n                        \"  (?:                            # AUX, NUL, COM1, COM2, COM3, COM4, \\n\" +\r\n                        \"    CON|PRN|AUX|NUL|             # COM5, COM6, COM7, COM8, COM9,     \\n\" +\r\n                        \"    COM[1-9]|LPT[1-9]            # LPT1, LPT2, LPT3, LPT4, LPT5,     \\n\" +\r\n                        \"  )                              # LPT6, LPT7, LPT8, and LPT9...     \\n\" +\r\n                        \"  (?:\\\\.[^.]*)?                  # followed by optional extension    \\n\" +\r\n                        \"  $                              # and end of string                 \\n\" +\r\n                        \")                                # End negative lookahead assertion. \\n\" +\r\n                        \"[^<>:\\\"/\\\\\\\\|?*\\\\x00-\\\\x1F]*     # Zero or more valid filename chars.\\n\" +\r\n                        \"[^<>:\\\"/\\\\\\\\|?*\\\\x00-\\\\x1F\\\\ .]  # Last char is not a space or dot.  \\n\" +\r\n                        \"$                                # Anchor to end of string.            \",\r\n                Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.COMMENTS);\r\n        Matcher matcher = pattern.matcher(text);\r\n        boolean isMatch = matcher.matches();\r\n        return isMatch;\r\n    }\r\n\r\n    public static void deleteFile(File f) {\r\n        if (f.exists()) {\r\n            if (f.isDirectory()) {\r\n                File[] files = f.listFiles();\r\n                for (int i = 0; i < files.length; i++) {\r\n                    deleteFile(files[i]);\r\n                }\r\n            }\r\n            f.delete();\r\n        }\r\n    }\r\n\r\n    public static String getFileMD5String(File f) {\r\n        byte[] buffer = new byte[4 * 1024];\r\n        BufferedInputStream bis = null;\r\n        try {\r\n            MessageDigest digest = MessageDigest.getInstance(\"MD5\");\r\n            bis = new BufferedInputStream(new FileInputStream(f));\r\n            int lent;\r\n            while ((lent = bis.read(buffer)) != -1) {\r\n                digest.update(buffer, 0, lent);\r\n            }\r\n            byte[] hash = digest.digest();\r\n            StringBuilder hex = new StringBuilder(hash.length * 2);\r\n            for (byte b : hash) {\r\n                if ((b & 0xFF) < 0x10) hex.append(\"0\");\r\n                hex.append(Integer.toHexString(b & 0xFF));\r\n            }\r\n            return hex.toString();\r\n        } catch (IOException e) {\r\n            e.printStackTrace();\r\n        } catch (NoSuchAlgorithmException e) {\r\n            e.printStackTrace();\r\n        } finally {\r\n            FileTool.closeIO(bis);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static String md5(String string) {\r\n        byte[] hash;\r\n        try {\r\n            hash = MessageDigest.getInstance(\"MD5\").digest(string.getBytes(\"UTF-8\"));\r\n        } catch (NoSuchAlgorithmException e) {\r\n            throw new RuntimeException(\"Huh, MD5 should be supported?\", e);\r\n        } catch (UnsupportedEncodingException e) {\r\n            throw new RuntimeException(\"Huh, UTF-8 should be supported?\", e);\r\n        }\r\n\r\n        StringBuilder hex = new StringBuilder(hash.length * 2);\r\n        for (byte b : hash) {\r\n            if ((b & 0xFF) < 0x10) hex.append(\"0\");\r\n            hex.append(Integer.toHexString(b & 0xFF));\r\n        }\r\n        return hex.toString();\r\n    }\r\n\r\n    /**\r\n     * <p>\r\n     * 判断照片角度\r\n     * </p>\r\n     *\r\n     * @param path @return int @throws\r\n     */\r\n    public static int readPictureDegree(String path) {\r\n        int degree = 0;\r\n        try {\r\n            ExifInterface exifInterface = new ExifInterface(path);\r\n            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,\r\n                    ExifInterface.ORIENTATION_NORMAL);\r\n            switch (orientation) {\r\n                case ExifInterface.ORIENTATION_ROTATE_90:\r\n                    degree = 90;\r\n                    break;\r\n                case ExifInterface.ORIENTATION_ROTATE_180:\r\n                    degree = 180;\r\n                    break;\r\n                case ExifInterface.ORIENTATION_ROTATE_270:\r\n                    degree = 270;\r\n                    break;\r\n            }\r\n        } catch (IOException e) {\r\n            e.printStackTrace();\r\n        }\r\n        return degree;\r\n    }\r\n\r\n    public static String getFileExtension(File file) {\r\n        String fileName = file.getName();\r\n        return getFileExtension(fileName);\r\n    }\r\n\r\n    /**\r\n     * 获取文件扩展名\r\n     *\r\n     * @param fileName\r\n     * @return\r\n     */\r\n    public static String getFileExtension(String fileName) {\r\n\r\n        if (fileName.lastIndexOf(\".\") != -1 && fileName.lastIndexOf(\".\") != 0) {\r\n            return fileName.substring(fileName.lastIndexOf(\".\") + 1);\r\n        } else {\r\n            return \"\";\r\n        }\r\n    }\r\n\r\n    public static String urlDelStoken(String url) {\r\n        url = url.trim().toLowerCase();\r\n        // 是否为http并且是否包含Stoken\r\n        if (!(url.startsWith(\"http\") && url.contains(\"stoken\"))) {\r\n            return url;\r\n        }\r\n        String[] tampStrings = url.split(\"[?&]\");\r\n        for (String temp : tampStrings) {\r\n            if (temp.trim().startsWith(\"stoken=\")) {\r\n                url = url.replace(temp, \"\").replace(\"?&\", \"?\");\r\n                break;\r\n            }\r\n        }\r\n        return url;\r\n    }\r\n\r\n    /*******************************************************************************\r\n     * Copyright 2011-2014 Sergey Tarasevich\r\n     * <p/>\r\n     * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\r\n     * not use this file except in compliance with the License. You may obtain a\r\n     * copy of the License at\r\n     * <p/>\r\n     * http://www.apache.org/licenses/LICENSE-2.0\r\n     * <p/>\r\n     * Unless required by applicable law or agreed to in writing, software\r\n     * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\r\n     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r\n     * License for the specific language governing permissions and limitations\r\n     * under the License.\r\n     *******************************************************************************/\r\n\r\n    private static final String EXTERNAL_STORAGE_PERMISSION = \"android.permission.WRITE_EXTERNAL_STORAGE\";\r\n    private static final String INDIVIDUAL_DIR_NAME         = \"uil-images\";\r\n    private static final String INDIVIDUAL_LOG_FOLDER        = \"logs\";\r\n    private static final String INDIVIDUAL_UPLOAD_CACHE     = \"upload-cache\";\r\n    private static final String FILE_DIR_NAME               = \"file_cache\";\r\n    private static final String FILE_AUDIO_DIR_NAME         = \"Audio\";\r\n    private static final String FILE_IMAGE_DIR_NAME         = \"image\";\r\n\r\n    /**\r\n     * Returns application cache directory. Cache directory will be created on\r\n     * SD card <i>(\"/Android/data/[app_package_name]/cache\")</i> if card is\r\n     * mounted and app has appropriate permission. Else - Android defines cache\r\n     * directory on device's file system.\r\n     *\r\n     * @return Cache {@link File directory}.<br />\r\n     * <b>NOTE:</b> Can be null in some unpredictable cases (if SD card\r\n     * is unmounted and {@link Context#getCacheDir()\r\n     * Context.getCacheDir()} returns null).\r\n     */\r\n    public static File getCacheDirectory(Context context) {\r\n        return getCacheDirectory(context, true);\r\n    }\r\n\r\n    /**\r\n     * Returns application cache directory. Cache directory will be created on\r\n     * SD card <i>(\"/Android/data/[app_package_name]/cache\")</i> (if card is\r\n     * mounted and app has appropriate permission) or on device's file system\r\n     * depending incoming parameters.\r\n     *\r\n     * @param preferExternal Whether prefer external location for cache\r\n     * @return Cache {@link File directory}.<br />\r\n     * <b>NOTE:</b> Can be null in some unpredictable cases (if SD card\r\n     * is unmounted and {@link Context#getCacheDir()\r\n     * Context.getCacheDir()} returns null).\r\n     */\r\n    public static File getCacheDirectory(Context context, boolean preferExternal) {\r\n        assert context != null;\r\n        File appCacheDir = null;\r\n        String externalStorageState;\r\n        try {\r\n            externalStorageState = Environment.getExternalStorageState();\r\n        } catch (NullPointerException e) { // (sh)it happens (Issue #660)\r\n            externalStorageState = \"\";\r\n        } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue\r\n            // #989)\r\n            externalStorageState = \"\";\r\n        }\r\n        if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {\r\n            appCacheDir = getExternalCacheDir(context);\r\n        }\r\n        if (appCacheDir == null) {\r\n            appCacheDir = context.getCacheDir();\r\n        }\r\n        if (appCacheDir == null) {\r\n            String cacheDirPath = \"/data/data/\" + context.getPackageName() + \"/cache/\";\r\n            appCacheDir = new File(cacheDirPath);\r\n        }\r\n        return appCacheDir;\r\n    }\r\n\r\n    /**\r\n     * Returns individual application cache directory (for only image caching\r\n     * from ImageLoader). Cache directory will be created on SD card\r\n     * <i>(\"/Android/data/[app_package_name]/cache/uil-images\")</i> if card is\r\n     * mounted and app has appropriate permission. Else - Android defines cache\r\n     * directory on device's file system.\r\n     *\r\n     * @return Cache {@link File directory}\r\n     */\r\n    public static File getIndividualImageCacheDirectory(Context context) {\r\n        return getIndividualCacheDirectory(context, INDIVIDUAL_DIR_NAME);\r\n    }\r\n\r\n    public static File getIndividualUploadCacheDirectory(Context context) {\r\n        return getIndividualCacheDirectory(context, INDIVIDUAL_UPLOAD_CACHE);\r\n    }\r\n\r\n    public static File getIndividualLogCacheDirectory(Context context) {\r\n        return getIndividualCacheDirectory(context, INDIVIDUAL_LOG_FOLDER);\r\n    }\r\n\r\n\r\n    public static File getIndividualAudioCacheDirectory(Context context) {\r\n        File file = new File(getIndividualCacheDirectory(context, FILE_DIR_NAME), FILE_AUDIO_DIR_NAME);\r\n        if(!file.exists()) file.mkdirs();\r\n        return file;\r\n    }\r\n\r\n    public static File getIndividualImageFileDirectory(Context context) {\r\n        return new File(getIndividualCacheDirectory(context, FILE_DIR_NAME), FILE_IMAGE_DIR_NAME);\r\n    }\r\n\r\n    /**\r\n     * Returns individual application cache directory (for only image caching\r\n     * from ImageLoader). Cache directory will be created on SD card\r\n     * <i>(\"/Android/data/[app_package_name]/cache/uil-images\")</i> if card is\r\n     * mounted and app has appropriate permission. Else - Android defines cache\r\n     * directory on device's file system.\r\n     *\r\n     * @param cacheDir Cache directory path (e.g.: \"AppCacheDir\",\r\n     *                 \"AppDir/cache/images\")\r\n     * @return Cache {@link File directory}\r\n     */\r\n    public static File getIndividualCacheDirectory(Context context, String cacheDir) {\r\n        File appCacheDir = getCacheDirectory(context);\r\n        File individualCacheDir = new File(appCacheDir, cacheDir);\r\n        if (!individualCacheDir.exists()) {\r\n            if (!individualCacheDir.mkdir()) {\r\n                individualCacheDir = appCacheDir;\r\n            }\r\n            try {\r\n                new File(individualCacheDir, \".nomedia\").createNewFile();\r\n            } catch (IOException e) {\r\n                e.printStackTrace();\r\n            }\r\n        }\r\n        return individualCacheDir;\r\n    }\r\n\r\n    /**\r\n     * Returns specified application cache directory. Cache directory will be\r\n     * created on SD card by defined path if card is mounted and app has\r\n     * appropriate permission. Else - Android defines cache directory on\r\n     * device's file system.\r\n     *\r\n     * @param cacheDir Cache directory path (e.g.: \"AppCacheDir\",\r\n     *                 \"AppDir/cache/images\")\r\n     * @return Cache {@link File directory}\r\n     */\r\n    public static File getOwnCacheDirectory(Context context, String cacheDir) {\r\n        File appCacheDir = null;\r\n        if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) {\r\n            appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);\r\n        }\r\n        if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) {\r\n            appCacheDir = context.getCacheDir();\r\n        }\r\n        return appCacheDir;\r\n    }\r\n\r\n    /**\r\n     * Returns specified application cache directory. Cache directory will be\r\n     * created on SD card by defined path if card is mounted and app has\r\n     * appropriate permission. Else - Android defines cache directory on\r\n     * device's file system.\r\n     *\r\n     * @param cacheDir Cache directory path (e.g.: \"AppCacheDir\",\r\n     *                 \"AppDir/cache/images\")\r\n     * @return Cache {@link File directory}\r\n     */\r\n    public static File getOwnCacheDirectory(Context context, String cacheDir, boolean preferExternal) {\r\n        assert context != null;\r\n        File appCacheDir = null;\r\n        if (preferExternal && MEDIA_MOUNTED.equals(Environment.getExternalStorageState())\r\n                && hasExternalStoragePermission(context)) {\r\n            appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);\r\n        }\r\n        if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) {\r\n            appCacheDir = context.getCacheDir();\r\n        }\r\n        return appCacheDir;\r\n    }\r\n\r\n    @SuppressLint(\"NewApi\")\r\n    public static long getDiskSizeRemain(Context context) {\r\n        File path = getIndividualImageCacheDirectory(context);\r\n        StatFs stat = new StatFs(path.getPath());\r\n        long result;\r\n        long block;\r\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {\r\n            result = stat.getAvailableBlocksLong();\r\n            block = stat.getBlockSizeLong();\r\n        } else {\r\n            result = stat.getAvailableBlocks();\r\n            block = stat.getBlockSize();\r\n        }\r\n        return result * block;\r\n    }\r\n\r\n    private static File getExternalCacheDir(Context context) {\r\n        assert context != null;\r\n        File dataDir = new File(new File(Environment.getExternalStorageDirectory(), \"Android\"), \"data\");\r\n        File appCacheDir = new File(new File(dataDir, context.getPackageName()), \"cache\");\r\n        if (!appCacheDir.exists()) {\r\n            if (!appCacheDir.mkdirs()) {\r\n                return null;\r\n            }\r\n            try {\r\n                new File(appCacheDir, \".nomedia\").createNewFile();\r\n            } catch (IOException e) {\r\n            }\r\n        }\r\n        return appCacheDir;\r\n    }\r\n\r\n    private static boolean hasExternalStoragePermission(Context context) {\r\n        assert context != null;\r\n        int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);\r\n        return perm == PackageManager.PERMISSION_GRANTED;\r\n    }\r\n\r\n    public static void closeIO(Closeable... io) {\r\n        for (Closeable close : io) {\r\n            if (close != null) {\r\n                try {\r\n                    close.close();\r\n                    close = null;\r\n                } catch (IOException e) {\r\n                    e.printStackTrace();\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public static String getMimeType(String path) {\r\n        String defaultContentType = \"application/octet-stream\";\r\n        if (TextUtils.isEmpty(path))\r\n            return defaultContentType;\r\n        FileNameMap fileNameMap = URLConnection.getFileNameMap();\r\n        String contentTypeFor = null;\r\n        try {\r\n            contentTypeFor = fileNameMap.getContentTypeFor(path);\r\n        } catch (Throwable t) {\r\n            PrintStream stream = null;\r\n//            try {\r\n//                stream = LogUtils.getErrorPrintStream();\r\n//                if (stream != null) {\r\n//                    t.printStackTrace(stream);\r\n//                }\r\n//                contentTypeFor = getMimeType1(path);\r\n//            } catch (Throwable e) {\r\n//                e.printStackTrace();\r\n//            } finally {\r\n//                closeIO(stream);\r\n//            }\r\n        }\r\n        return TextUtils.isEmpty(contentTypeFor) ? defaultContentType : contentTypeFor;\r\n    }\r\n\r\n    /**\r\n     * @return The MIME type for the given file.\r\n     */\r\n    public static String getMimeType(File file) {\r\n        String extension = getFileExtension(file.getName());\r\n        return getMimeTypeByExtension(extension);\r\n//        if (extension.length() > 0)\r\n//            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));\r\n//        return \"application/octet-stream\";\r\n    }\r\n\r\n    /**\r\n     * @return The MIME type for the given file.\r\n     */\r\n    public static String getMimeType1(String path) {\r\n        String extension = getFileExtension(path);\r\n        return getMimeTypeByExtension(extension);\r\n//        if (extension.length() > 0)\r\n//            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));\r\n//        return \"application/octet-stream\";\r\n    }\r\n\r\n    /**\r\n     * @return The MIME type for the given file.\r\n     */\r\n    private static String getMimeTypeByExtension(String extension) {\r\n        if (extension.length() > 0)\r\n            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);\r\n        return \"application/octet-stream\";\r\n    }\r\n\r\n    public static String getDefaultSavePath() {\r\n        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), \"oMiniBox\");\r\n        if (!file.exists()) {\r\n            file.mkdirs();\r\n        }\r\n        return file.getAbsolutePath();\r\n    }\r\n\r\n    /* Checks if external storage is available for read and write */\r\n    public static boolean isExternalStorageWritable() {\r\n        String state = Environment.getExternalStorageState();\r\n        if (Environment.MEDIA_MOUNTED.equals(state)) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    /* Checks if external storage is available to at least read */\r\n    public static boolean isExternalStorageReadable() {\r\n        String state = Environment.getExternalStorageState();\r\n        if (Environment.MEDIA_MOUNTED.equals(state) ||\r\n                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static long getFolderSize(File file) {\r\n        long size = 0;\r\n        File[] fileList = file.listFiles();\r\n        for (int i = 0; i < fileList.length; i++) {\r\n            if (fileList[i].isDirectory()) {\r\n                size = size + getFolderSize(fileList[i]);\r\n            } else {\r\n                size = size + fileList[i].length();\r\n            }\r\n        }\r\n        return size;\r\n    }\r\n\r\n\r\n    public static void deleteFolderFile(String filePath, boolean deleteThisPath)\r\n            throws IOException {\r\n        if (!TextUtils.isEmpty(filePath)) {\r\n            File file = new File(filePath);\r\n\r\n            if (file.isDirectory()) {// 处理目录\r\n                File files[] = file.listFiles();\r\n                for (int i = 0; i < files.length; i++) {\r\n                    deleteFolderFile(files[i].getAbsolutePath(), true);\r\n                }\r\n            }\r\n            if (deleteThisPath) {\r\n                if (!file.isDirectory()) {// 如果是文件，删除\r\n                    file.delete();\r\n                } else {// 目录\r\n                    if (file.listFiles().length == 0) {// 目录下没有文件或者目录，删除\r\n                        file.delete();\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/LogUtils.java",
    "content": "/*\r\n * Copyright 2014 Google Inc. All rights reserved.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage record.wilson.flutter.com.flutter_plugin_record.utils;\r\n\r\nimport android.content.Context;\r\nimport android.util.Log;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.BufferedWriter;\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.FileOutputStream;\r\nimport java.io.FileReader;\r\nimport java.io.FileWriter;\r\nimport java.io.IOException;\r\nimport java.io.PrintStream;\r\nimport java.util.Date;\r\n\r\npublic class LogUtils {\r\n    public static String LOG_PREFIX         = \"\";\r\n    private static int    LOG_PREFIX_LENGTH  = LOG_PREFIX.length();\r\n    private static int    MAX_LOG_TAG_LENGTH = 23;\r\n\r\n    public static boolean LOGGING_ENABLED = true;\r\n\r\n    public static String makeLogTag(String str) {\r\n        if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {\r\n            return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);\r\n        }\r\n        return LOG_PREFIX + str;\r\n    }\r\n\r\n    /**\r\n     * Don't use this when obfuscating class names!\r\n     */\r\n    public static String makeLogTag(Class cls) {\r\n        return makeLogTag(cls.getSimpleName());\r\n    }\r\n\r\n    public static String getTAG() {\r\n        return LOG_PREFIX;\r\n    }\r\n\r\n    public static void LOGD(String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.d(getTag(), message != null ? message : \"message is null\");\r\n        }\r\n    }\r\n\r\n    public static void LOGD(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            if (Log.isLoggable(tag, Log.DEBUG)) {\r\n                Log.d(tag, message);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void LOGDPrintProcess(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            if (Log.isLoggable(tag, Log.DEBUG)) {\r\n                String msg = getLogMessage(message);\r\n                Log.d(tag, msg);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void LOGD(final String tag, String message, Throwable cause) {\r\n        if (LOGGING_ENABLED) {\r\n            if (Log.isLoggable(tag, Log.DEBUG)) {\r\n                Log.d(tag, message, cause);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void LOGV(String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.v(getTag(), message != null ? message : \"message is null\");\r\n        }\r\n    }\r\n\r\n    public static void LOGV(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            if (Log.isLoggable(tag, Log.VERBOSE)) {\r\n                Log.v(tag, message);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void LOGV(final String tag, String message, Throwable cause) {\r\n        if (LOGGING_ENABLED) {\r\n            if (Log.isLoggable(tag, Log.VERBOSE)) {\r\n                Log.v(tag, message, cause);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void LOGI(String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.i(getTag(), message != null ? message : \"message is null\");\r\n        }\r\n    }\r\n\r\n    public static void LOGI(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.i(tag, message);\r\n        }\r\n    }\r\n\r\n    public static void LOGI(final String tag, String message, Throwable cause) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.i(tag, message, cause);\r\n        }\r\n    }\r\n\r\n    public static void LOGW(String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.w(getTag(), message != null ? message : \"message is null\");\r\n        }\r\n    }\r\n\r\n    public static void LOGW(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.w(tag, message);\r\n        }\r\n    }\r\n\r\n    public static void LOGW(final String tag, String message, Throwable cause) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.w(tag, message, cause);\r\n        }\r\n    }\r\n\r\n    public static void LOGE(String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.e(getTag(), message != null ? message : \"message is null\");\r\n        }\r\n    }\r\n\r\n    public static void LOGE(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.e(tag, message);\r\n        }\r\n    }\r\n\r\n    private static final int MAX_CHAR_ONE_LINE = 82;\r\n\r\n    public static void LOGEPrintProcess(final String tag, String message) {\r\n        if (LOGGING_ENABLED) {\r\n            String msg = getLogMessage(message);\r\n            Log.e(tag, msg);\r\n        }\r\n    }\r\n\r\n    private static final int LOG_LENGTH = 100;\r\n\r\n    private static String getLogMessage(String msg) {\r\n        StringBuilder sb = new StringBuilder();\r\n        String[] split = msg.split(\"\\n\");\r\n        addPrintHead(sb);\r\n        sb.append(\"\\n\")\r\n                .append(getFullFillTopLine(new Date().toString(), LOG_PREFIX))\r\n                .append(\"\\n\");\r\n        for (int i = 0; i < split.length; i++) {\r\n            sb.append(getLine(split[i])).append(\"\\n\");\r\n        }\r\n        sb.append(getFullFillBottomLine(\"pid : \" + android.os.Process.myPid(), \"tid : \" + Thread.currentThread().getId())).append(\"\\n\");\r\n        addPrintBottom(sb);\r\n        return sb.toString();\r\n    }\r\n\r\n    private static void addPrintBottom(StringBuilder sb) {\r\n        sb.append(\"└\");\r\n        fillLine(sb, LOG_LENGTH, \"-\");\r\n        sb.append(\"┘\");\r\n    }\r\n\r\n    private static final int PADDING_LEFT = 4;\r\n\r\n    private static String getLine(String s) {\r\n        int remind = LOG_LENGTH - 4 - s.getBytes().length - PADDING_LEFT;\r\n        StringBuilder temp = new StringBuilder();\r\n        temp.append(\"├┤\");\r\n        for (int j = 0; j < PADDING_LEFT; j++) {\r\n            temp.append(\" \");\r\n        }\r\n        temp.append(s);\r\n        fillLine(temp, remind, \" \");\r\n        temp.append(\"├┤\");\r\n        return temp.toString();\r\n    }\r\n\r\n    private static final int POSITION_TOP    = 0;\r\n    private static final int POSITION_MIDDLE = 1;\r\n    private static final int POSITION_BOTTOM = 2;\r\n\r\n    private static String getFullFillTopLine(String... s) {\r\n        return getFullFillLine(POSITION_TOP, s);\r\n    }\r\n\r\n    private static String getFullFillBottomLine(String... s) {\r\n        return getFullFillLine(POSITION_BOTTOM, s);\r\n    }\r\n\r\n    private static String getFullFillLine(int position, String... s) {\r\n        int length = 0;\r\n        for (int i = 0; i < s.length; i++) {\r\n            int l = s[i].getBytes().length;\r\n            length += l;\r\n        }\r\n        int remind = LOG_LENGTH - length - 4;\r\n        int each = remind / (s.length + 1);\r\n        int fix = each * (s.length + 1) - remind;\r\n        StringBuilder temp = new StringBuilder();\r\n        switch (position) {\r\n            case POSITION_TOP:\r\n                temp.append(\"├┬\");\r\n                break;\r\n            case POSITION_MIDDLE:\r\n                temp.append(\"├┤\");\r\n                break;\r\n            case POSITION_BOTTOM:\r\n                temp.append(\"├┴\");\r\n                break;\r\n        }\r\n        for (int j = 0; j < s.length; j++) {\r\n            fillLine(temp, each, \"-\");\r\n            temp.append(s[j]);\r\n        }\r\n        fillLine(temp, each - fix, \"-\");\r\n        switch (position) {\r\n            case POSITION_TOP:\r\n                temp.append(\"┬┤\");\r\n                break;\r\n            case POSITION_MIDDLE:\r\n                temp.append(\"├┤\");\r\n                break;\r\n            case POSITION_BOTTOM:\r\n                temp.append(\"┴┤\");\r\n                break;\r\n        }\r\n        return temp.toString();\r\n    }\r\n\r\n    private static void fillLine(StringBuilder sb, int length, String filler) {\r\n        int fill = length / filler.length();\r\n        for (int k = 0; k < fill; k++) {\r\n            sb.append(filler);\r\n        }\r\n    }\r\n\r\n    private static void addPrintHead(StringBuilder sb) {\r\n        sb.append(\"┌\");\r\n        fillLine(sb, LOG_LENGTH, \"-\");\r\n        sb.append(\"┐\");\r\n    }\r\n\r\n\r\n    public static void LOGE(final String tag, String message, Throwable cause) {\r\n        if (LOGGING_ENABLED) {\r\n            Log.e(tag, message, cause);\r\n        }\r\n    }\r\n\r\n    public static void write2Log(Context context, String msg) {\r\n        write2Log(context, msg, new StringBuilder(new Date().toString()).append(\"_log\").toString());\r\n    }\r\n\r\n    public static PrintStream getErrorPrintStream(Context context) throws FileNotFoundException {\r\n        File logFile = new File(FileTool.getIndividualLogCacheDirectory(context), new StringBuilder(new Date().toString()).append(\"_log\").toString() + \".txt\");\r\n        return new PrintStream(new FileOutputStream(logFile));\r\n    }\r\n\r\n    public static void write2Log(Context context, String msg, String name) {\r\n        if (LOGGING_ENABLED) {\r\n            File logFile = new File(FileTool.getCacheDirectory(context), name + \".txt\");\r\n            BufferedWriter writer = null;\r\n            try {\r\n                if (!logFile.exists()) logFile.createNewFile();\r\n                writer = new BufferedWriter(new FileWriter(logFile, true));\r\n                writer.newLine();\r\n                writer.append(msg);\r\n            } catch (IOException e) {\r\n                e.printStackTrace();\r\n            } finally {\r\n                FileTool.closeIO(writer);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    private static String getTag() {\r\n        StackTraceElement[] trace = new Throwable().fillInStackTrace()\r\n                .getStackTrace();\r\n        String callingClass = \"\";\r\n        for (int i = 2; i < trace.length; i++) {\r\n            Class<?> clazz = trace[i].getClass();\r\n            if (!clazz.equals(Log.class)) {\r\n                callingClass = trace[i].getClassName();\r\n                callingClass = callingClass.substring(callingClass\r\n                        .lastIndexOf('.') + 1);\r\n                break;\r\n            }\r\n        }\r\n        return callingClass;\r\n    }\r\n\r\n    private LogUtils() {\r\n    }\r\n\r\n    public static IntervalCounter getIntervalCounter() {\r\n        return new IntervalCounter();\r\n    }\r\n\r\n    public static class IntervalCounter {\r\n        private long timeStamps;\r\n\r\n        private IntervalCounter() {\r\n            timeStamps = System.currentTimeMillis();\r\n        }\r\n\r\n        public long getInterval() {\r\n            long result = System.currentTimeMillis() - timeStamps;\r\n            timeStamps = System.currentTimeMillis();\r\n            return result;\r\n        }\r\n\r\n        public long getTimeStamps() {\r\n            timeStamps = System.currentTimeMillis();\r\n            return timeStamps;\r\n        }\r\n\r\n        public String getIntervalStr() {\r\n            return \" interval is \" + getInterval();\r\n        }\r\n    }\r\n\r\n    public static String getCurCpuFreq() {\r\n        String result = \"N/A\";\r\n        try {\r\n            FileReader fr = new FileReader(\r\n                    \"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq\");\r\n            BufferedReader br = new BufferedReader(fr);\r\n            String text = br.readLine();\r\n            result = text.trim();\r\n        } catch (FileNotFoundException e) {\r\n            e.printStackTrace();\r\n        } catch (IOException e) {\r\n            e.printStackTrace();\r\n        }\r\n        return result;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayState.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\npublic enum PlayState {\n    prepare, start, pause, complete\n}\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayUtilsPlus.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.media.MediaPlayer;\n\n\npublic class PlayUtilsPlus {\n    PlayStateChangeListener playStateChangeListener;\n    MediaPlayer player;\n\n    public PlayUtilsPlus() {\n    }\n\n    public void setPlayStateChangeListener(PlayStateChangeListener listener) {\n        this.playStateChangeListener = listener;\n        //  this.playStateChangeListener.onPlayStateChange(PlayState.prepare);\n    }\n\n    public void startPlaying(String filePath) {\n        try {\n            isPause=false;\n            this.player = new MediaPlayer();\n            this.player.setDataSource(filePath);\n            this.player.prepareAsync();\n            this.player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {\n                public void onPrepared(MediaPlayer mp) {\n                    PlayUtilsPlus.this.player.start();\n                }\n            });\n            if (this.playStateChangeListener != null) {\n                // this.playStateChangeListener.onPlayStateChange(PlayState.start);\n            }\n\n            this.player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {\n                public void onCompletion(MediaPlayer mp) {\n                    PlayUtilsPlus.this.stopPlaying();\n                }\n            });\n        } catch (Exception var3) {\n            var3.printStackTrace();\n        }\n\n    }\n    Boolean isPause = false;\n\n    public boolean pausePlay() {\n        try {\n            if (this.player.isPlaying() && !isPause) {\n                this.player.pause();\n                isPause = true;\n            } else {\n                this.player.start();\n                isPause = false;\n            }\n\n        } catch (Exception var2) {\n            var2.printStackTrace();\n        }\n        return isPause ;\n    }\n\n    public void stopPlaying() {\n        try {\n            if (this.player != null) {\n                this.player.stop();\n                this.player.reset();\n                this.player=null;\n                if (this.playStateChangeListener != null) {\n                    this.playStateChangeListener.onPlayStateChange(PlayState.complete);\n                }\n            }\n        } catch (Exception var2) {\n            var2.printStackTrace();\n        }\n\n    }\n\n    public boolean isPlaying() {\n        try {\n            return this.player != null && this.player.isPlaying();\n        } catch (Exception var2) {\n            return false;\n        }\n    }\n\n    public interface PlayStateChangeListener {\n        void onPlayStateChange(PlayState playState);\n    }\n}\n\n\n"
  },
  {
    "path": "android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/RecorderUtil.java",
    "content": "package record.wilson.flutter.com.flutter_plugin_record.utils;\n\nimport android.os.Environment;\nimport android.util.Log;\n\nimport com.maple.recorder.recording.AudioChunk;\nimport com.maple.recorder.recording.AudioRecordConfig;\nimport com.maple.recorder.recording.MsRecorder;\nimport com.maple.recorder.recording.PullTransport;\nimport com.maple.recorder.recording.Recorder;\n\nimport java.io.File;\nimport java.util.Date;\n\n\npublic class RecorderUtil {\n\n    Recorder recorder;\n    public static String rootPath = \"/yun_ke_fu/flutter/wav_file/\";\n    String voicePath;\n    PlayUtilsPlus playUtils;\n\n    RecordListener recordListener;\n    PlayStateListener playStateListener;\n\n    public RecorderUtil() {\n        initVoice();\n    }\n\n\n\n    public RecorderUtil(String path) {\n        voicePath =path;\n    }\n\n\n\n\n\n    public void addPlayAmplitudeListener(RecordListener recordListener) {\n        this.recordListener = recordListener;\n    }\n    public void addPlayStateListener(PlayStateListener playStateListener) {\n        this.playStateListener = playStateListener;\n    }\n\n    private void initVoice() {\n        initPath();\n        initVoicePath();\n        initRecorder();\n    }\n\n\n\n    //初始化存储路径\n    private void initPath() {\n        String ROOT = \"\";// /storage/emulated/0\n        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {\n            ROOT = Environment.getExternalStorageDirectory().getPath();\n            Log.e(\"voice\", \"系统方法：\" + ROOT);\n        }\n        rootPath = ROOT + rootPath;\n\n        File lrcFile = new File(rootPath);\n        if (!lrcFile.exists()) {\n            lrcFile.mkdirs();\n        }\n\n        Log.e(\"voice\", \"初始存储路径\" + rootPath);\n    }\n\n\n    private void initVoicePath() {\n        String forDate = DateUtils.dateToString(new Date());\n        String name = \"wav-\" + forDate;\n        voicePath = rootPath + name + \".wav\";\n        Log.e(\"voice\", \"初始化语音路径\" + voicePath);\n\n\n    }\n\n\n    private void initRecorder() {\n        recorder = MsRecorder.wav(\n                new File(voicePath),\n                new AudioRecordConfig.Default(),\n                new PullTransport.Default()\n                        .setOnAudioChunkPulledListener(new PullTransport.OnAudioChunkPulledListener() {\n                            @Override\n                            public void onAudioChunkPulled(AudioChunk audioChunk) {\n                                if (recordListener != null) {\n                                    recordListener.onPlayAmplitude(audioChunk.maxAmplitude());\n                                }\n                            }\n                        })\n\n        );\n    }\n\n    public void startRecord() {\n        if (recordListener != null) {\n            recordListener.onVoicePathSuccess(voicePath);\n        }\n\n        recorder.stopRecording();\n        recorder.startRecording();\n    }\n\n    public void stopRecord() {\n        recorder.stopRecording();\n    }\n\n    public void playVoice() {\n        if (playUtils == null) {\n            playUtils = new PlayUtilsPlus();\n            playUtils.setPlayStateChangeListener(new PlayUtilsPlus.PlayStateChangeListener() {\n                @Override\n                public void onPlayStateChange(PlayState playState) {\n                    playStateListener.playState(playState);\n                }\n            });\n        }\n        if(playUtils.isPlaying()) {\n            playUtils.stopPlaying();\n        }\n        playUtils.startPlaying(voicePath);\n    }\n\n    public  boolean pausePlay(){\n        LogUtils.LOGD(\"wilson\",\"pausePlay\");\n        boolean isPlaying = playUtils.pausePlay();\n        return isPlaying;\n    }\n\n    public  void stopPlay(){\n        LogUtils.LOGD(\"wilson\",\"stopPlay\");\n        playUtils.stopPlaying();\n    }\n\n    public interface RecordListener {\n        void onPlayAmplitude(Double amplitude);\n\n        void onVoicePathSuccess(String voicePath);\n\n    }\n\n    public interface PlayStateListener {\n\n        void playState(PlayState playState);\n\n    }\n}\n"
  },
  {
    "path": "example/.flutter-plugins-dependencies",
    "content": "{\"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\"}"
  },
  {
    "path": "example/.gitignore",
    "content": "# 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*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/Flutter/flutter_export_environment.sh\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n"
  },
  {
    "path": "example/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: 68587a0916366e9512a78df22c44163d041dd5f3\n  channel: stable\n\nproject_type: app\n"
  },
  {
    "path": "example/README.md",
    "content": "# flutter_plugin_record_example\n\n\n### 使用\n\n### 1, 初始化录制\n\n\n\n可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化\n\n    //实例化对象 \n    FlutterPluginRecord   recordPlugin = new FlutterPluginRecord();\n    //    初始化\n    recordPlugin.init()\n\n### 2, 开始录制\n   \n     recordPlugin.start()\n### 3, 停止录制\n     recordPlugin.stop()\n### 4, 播放 暂停,停止播放\n#### 1,播放\n     \n     recordPlugin.play()\n     \n#### 2, 暂停和继续播放\n       \n     recordPlugin.pausePlay();\n\n#### 3, 停止播放\n    \n     recordPlugin.stopPlay();\n      \n### 3, 释放资源\n可以在页面退出的时候进行资源释放 比如在  dispose方法中调用如下代码\n\n     recordPlugin.dispose()\n     \n### 4,回调监听  \n1,初始化回调监听  \n\n  \n    ///初始化方法的监听\n    recordPlugin.responseFromInit.listen((data) {\n      if (data) {\n        print(\"初始化成功\");\n      } else {\n        print(\"初始化失败\");\n      }\n    });\n    \n\n2,开始录制停止录制监听\n\n     /// 开始录制或结束录制的监听\n        recordPlugin.response.listen((data) {\n          if (data.msg == \"onStop\") {\n            ///结束录制时会返回录制文件的地址方便上传服务器\n            print(\"onStop  \" + data.path);\n          } else if (data.msg == \"onStart\") {\n            print(\"onStart --\");\n          }\n        });\n    \n3,录制声音大小回调监听\n\n\n     ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式\n        recordPlugin.responseFromAmplitude.listen((data) {\n          var voiceData = double.parse(data.msg);\n          var tempVoice = \"\";\n          if (voiceData > 0 && voiceData < 0.1) {\n            tempVoice = \"images/voice_volume_2.png\";\n          } else if (voiceData > 0.2 && voiceData < 0.3) {\n            tempVoice = \"images/voice_volume_3.png\";\n          } else if (voiceData > 0.3 && voiceData < 0.4) {\n            tempVoice = \"images/voice_volume_4.png\";\n          } else if (voiceData > 0.4 && voiceData < 0.5) {\n            tempVoice = \"images/voice_volume_5.png\";\n          } else if (voiceData > 0.5 && voiceData < 0.6) {\n            tempVoice = \"images/voice_volume_6.png\";\n          } else if (voiceData > 0.6 && voiceData < 0.7) {\n            tempVoice = \"images/voice_volume_7.png\";\n          } else if (voiceData > 0.7 && voiceData < 1) {\n            tempVoice = \"images/voice_volume_7.png\";\n          }\n          setState(() {\n            voiceIco = tempVoice;\n            if(overlayEntry!=null){\n              overlayEntry.markNeedsBuild();\n            }\n          });\n    \n          print(\"振幅大小   \" + voiceData.toString() + \"  \" + voiceIco);\n        });\n    \n     \n## 2,录制组件的使用\n\n\n### 1,在使用的页面进行导入package\n\n    import 'package:flutter_plugin_record/index.dart';  \n        \n    \n    \n\n    \n### 2,在使用的地方引入VoiceWidget组件\n    \n    new VoiceWidget(),\n    \n    \n    \n## TODO\n\n* [x] 实现发送语音时间按下抬起时间很短提示\n* [x] 优化代码\n* [x] 实现录制完成文件路径回调功能,方面使用者可以把录音文件上传服务器\n\n\n## 关注公众号获取更多内容\n\n  ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190926100941125.jpg)\n\n"
  },
  {
    "path": "example/android/.gitignore",
    "content": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n\n# Remember to never publicly share your keystore.\n# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app\nkey.properties\n"
  },
  {
    "path": "example/android/app/build.gradle",
    "content": "def localProperties = new Properties()\ndef localPropertiesFile = rootProject.file('local.properties')\nif (localPropertiesFile.exists()) {\n    localPropertiesFile.withReader('UTF-8') { reader ->\n        localProperties.load(reader)\n    }\n}\n\ndef flutterRoot = localProperties.getProperty('flutter.sdk')\nif (flutterRoot == null) {\n    throw new GradleException(\"Flutter SDK not found. Define location with flutter.sdk in the local.properties file.\")\n}\n\ndef flutterVersionCode = localProperties.getProperty('flutter.versionCode')\nif (flutterVersionCode == null) {\n    flutterVersionCode = '1'\n}\n\ndef flutterVersionName = localProperties.getProperty('flutter.versionName')\nif (flutterVersionName == null) {\n    flutterVersionName = '1.0'\n}\n\napply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply from: \"$flutterRoot/packages/flutter_tools/gradle/flutter.gradle\"\n\nandroid {\n    compileSdkVersion 29\n\n    sourceSets {\n        main.java.srcDirs += 'src/main/kotlin'\n    }\n\n    lintOptions {\n        disable 'InvalidPackage'\n    }\n\n    defaultConfig {\n        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).\n        applicationId \"record.wilson.flutter.com.flutter_plugin_record_example\"\n        minSdkVersion 19\n        targetSdkVersion 28\n        versionCode flutterVersionCode.toInteger()\n        versionName flutterVersionName\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            // TODO: Add your own signing config for the release build.\n            // Signing with the debug keys for now, so `flutter run --release` works.\n            signingConfig signingConfigs.debug\n        }\n    }\n}\n\nflutter {\n    source '../..'\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.1'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'\n}\n"
  },
  {
    "path": "example/android/app/src/debug/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"record.wilson.flutter.com.flutter_plugin_record_example\">\n    <!-- Flutter needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "example/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"record.wilson.flutter.com.flutter_plugin_record_example\"\n    >\n\n    <!-- io.flutter.app.FlutterApplication is an android.app.Application that\n         calls FlutterMain.startInitialization(this); in its onCreate method.\n         In most cases you can leave this as-is, but you if you want to provide\n         additional functionality it is fine to subclass or reimplement\n         FlutterApplication and put your custom class here. -->\n    <application\n        android:requestLegacyExternalStorage=\"true\"\n        tools:replace=\"android:label\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"flutter_plugin_record_example\"\n        android:networkSecurityConfig=\"@xml/network_security_config\">\n        <meta-data\n                android:name=\"flutterEmbedding\"\n                android:value=\"2\" />\n        <activity\n            android:name=\".MainActivity\"\n            android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode\"\n            android:hardwareAccelerated=\"true\"\n            android:launchMode=\"singleTop\"\n            android:theme=\"@style/LaunchTheme\"\n            android:windowSoftInputMode=\"adjustResize\">\n            <!-- This keeps the window background of the activity showing\n                 until Flutter renders its first frame. It can be removed if\n                 there is no splash screen (such as the default splash screen\n                 defined in @style/LaunchTheme). -->\n            <meta-data\n                    android:name=\"io.flutter.embedding.android.SplashScreenDrawable\"\n                    android:resource=\"@drawable/launch_background\" />\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n</manifest>\n"
  },
  {
    "path": "example/android/app/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record_example/MainActivity.kt",
    "content": "package record.wilson.flutter.com.flutter_plugin_record_example\n\nimport io.flutter.embedding.android.FlutterActivity\n\nclass MainActivity: FlutterActivity() {\n\n}\n"
  },
  {
    "path": "example/android/app/src/main/res/drawable/launch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@android:color/white\" />\n\n    <!-- You can insert your own image assets here -->\n    <!-- <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@mipmap/launch_image\" />\n    </item> -->\n</layer-list>\n"
  },
  {
    "path": "example/android/app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <!-- Show a splash screen on the activity. Automatically removed when\n             Flutter draws its first frame -->\n        <item name=\"android:windowBackground\">@drawable/launch_background</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "example/android/app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\" />\n</network-security-config>\n"
  },
  {
    "path": "example/android/app/src/profile/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"record.wilson.flutter.com.flutter_plugin_record_example\">\n    <!-- Flutter needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "example/android/build.gradle",
    "content": "buildscript {\n//    ext.kotlin_version = '1.2.71'\n    ext.kotlin_version = '1.3.50'\n\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.5.3'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\nrootProject.buildDir = '../build'\nsubprojects {\n    project.buildDir = \"${rootProject.buildDir}/${project.name}\"\n}\nsubprojects {\n    project.evaluationDependsOn(':app')\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "example/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Dec 18 09:02:45 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.4.1-all.zip\n"
  },
  {
    "path": "example/android/gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx1536M\n\nandroid.enableR8=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n"
  },
  {
    "path": "example/android/res/values/strings_en.arb",
    "content": "{}"
  },
  {
    "path": "example/android/settings.gradle",
    "content": "include ':app'\n\ndef flutterProjectRoot = rootProject.projectDir.parentFile.toPath()\n\ndef plugins = new Properties()\ndef pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')\nif (pluginsFile.exists()) {\n    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }\n}\n\nplugins.each { name, path ->\n    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()\n    include \":$name\"\n    project(\":$name\").projectDir = pluginDirectory\n}\n"
  },
  {
    "path": "example/ios/Flutter/.last_build_id",
    "content": "dcb78416cfbfe60288e8c774d67c139c"
  },
  {
    "path": "example/ios/Flutter/AppFrameworkInfo.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>$(DEVELOPMENT_LANGUAGE)</string>\n  <key>CFBundleExecutable</key>\n  <string>App</string>\n  <key>CFBundleIdentifier</key>\n  <string>io.flutter.flutter.app</string>\n  <key>CFBundleInfoDictionaryVersion</key>\n  <string>6.0</string>\n  <key>CFBundleName</key>\n  <string>App</string>\n  <key>CFBundlePackageType</key>\n  <string>FMWK</string>\n  <key>CFBundleShortVersionString</key>\n  <string>1.0</string>\n  <key>CFBundleSignature</key>\n  <string>????</string>\n  <key>CFBundleVersion</key>\n  <string>1.0</string>\n  <key>MinimumOSVersion</key>\n  <string>8.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/ios/Flutter/Debug.xcconfig",
    "content": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "example/ios/Flutter/Flutter.podspec",
    "content": "#\n# NOTE: This podspec is NOT to be published. It is only used as a local source!\n#       This is a generated file; do not edit or check into version control.\n#\n\nPod::Spec.new do |s|\n  s.name             = 'Flutter'\n  s.version          = '1.0.0'\n  s.summary          = 'High-performance, high-fidelity mobile apps.'\n  s.homepage         = 'https://flutter.io'\n  s.license          = { :type => 'MIT' }\n  s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }\n  s.source           = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }\n  s.ios.deployment_target = '8.0'\n  # Framework linking is handled by Flutter tooling, not CocoaPods.\n  # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.\n  s.vendored_frameworks = 'path/to/nothing'\nend\n"
  },
  {
    "path": "example/ios/Flutter/Release.xcconfig",
    "content": "#include \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "example/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '9.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!\n\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "example/ios/Runner/AppDelegate.swift",
    "content": "import UIKit\nimport Flutter\n\n@UIApplicationMain\n@objc class AppDelegate: FlutterAppDelegate {\n  override func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n  ) -> Bool {\n    GeneratedPluginRegistrant.register(with: self)\n    return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n  }\n}\n"
  },
  {
    "path": "example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"83.5x83.5\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-83.5x83.5@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"1024x1024\",\n      \"idiom\" : \"ios-marketing\",\n      \"filename\" : \"Icon-App-1024x1024@1x.png\",\n      \"scale\" : \"1x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@3x.png\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "content": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou 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."
  },
  {
    "path": "example/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<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\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"12089\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"Ydg-fD-yQy\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"xbc-2k-c8Z\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView opaque=\"NO\" clipsSubviews=\"YES\" multipleTouchEnabled=\"YES\" contentMode=\"center\" image=\"LaunchImage\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"YRO-k0-Ey4\">\n                            </imageView>\n                        </subviews>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                        <constraints>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"1a2-6s-vTC\"/>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"4X2-HB-R7a\"/>\n                        </constraints>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"LaunchImage\" width=\"168\" height=\"185\"/>\n    </resources>\n</document>\n"
  },
  {
    "path": "example/ios/Runner/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<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\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"10085\"/>\n    </dependencies>\n    <scenes>\n        <!--Flutter View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"FlutterViewController\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"y3c-jy-aDJ\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"wfy-db-euE\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"600\" height=\"600\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"calibratedWhite\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "example/ios/Runner/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t</dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>flutter_plugin_record_example</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(FLUTTER_BUILD_NAME)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>打开话筒</string>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/ios/Runner/Runner-Bridging-Header.h",
    "content": "#import \"GeneratedPluginRegistrant.h\""
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };\n\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };\n\t\t481940F04663AD0B6D9C404B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6140FFF951F9E4BD7AB5108D /* Pods_Runner.framework */; };\n\t\t74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };\n\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };\n\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };\n\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t9705A1C41CF9048500538489 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = \"<group>\"; };\n\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = \"<group>\"; };\n\t\t1935C927EF54753EFE93F347 /* 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>\"; };\n\t\t26B885D5752B3F53CA047B5E /* 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>\"; };\n\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = \"<group>\"; };\n\t\t6140FFF951F9E4BD7AB5108D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"Runner-Bridging-Header.h\"; sourceTree = \"<group>\"; };\n\t\t74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = \"<group>\"; };\n\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = \"<group>\"; };\n\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = \"<group>\"; };\n\t\t97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tF345A59A40866AC079FB2659 /* 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>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t97C146EB1CF9000F007C117D /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t481940F04663AD0B6D9C404B /* Pods_Runner.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t61F2D205163EA3AEA9F3A435 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6140FFF951F9E4BD7AB5108D /* Pods_Runner.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9475E7547E3F8A95EB266079 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t26B885D5752B3F53CA047B5E /* Pods-Runner.debug.xcconfig */,\n\t\t\t\t1935C927EF54753EFE93F347 /* Pods-Runner.release.xcconfig */,\n\t\t\t\tF345A59A40866AC079FB2659 /* Pods-Runner.profile.xcconfig */,\n\t\t\t);\n\t\t\tname = Pods;\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9740EEB11CF90186004384FC /* Flutter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,\n\t\t\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */,\n\t\t\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */,\n\t\t\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */,\n\t\t\t);\n\t\t\tname = Flutter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146E51CF9000F007C117D = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9740EEB11CF90186004384FC /* Flutter */,\n\t\t\t\t97C146F01CF9000F007C117D /* Runner */,\n\t\t\t\t97C146EF1CF9000F007C117D /* Products */,\n\t\t\t\t9475E7547E3F8A95EB266079 /* Pods */,\n\t\t\t\t61F2D205163EA3AEA9F3A435 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146EF1CF9000F007C117D /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146EE1CF9000F007C117D /* Runner.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146F01CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FA1CF9000F007C117D /* Main.storyboard */,\n\t\t\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */,\n\t\t\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,\n\t\t\t\t97C147021CF9000F007C117D /* Info.plist */,\n\t\t\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,\n\t\t\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,\n\t\t\t\t74858FAE1ED2DC5600515810 /* AppDelegate.swift */,\n\t\t\t\t74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,\n\t\t\t);\n\t\t\tpath = Runner;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t97C146ED1CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t504AAE8408988510F90F1B85 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t9740EEB61CF901F6004384FC /* Run Script */,\n\t\t\t\t97C146EA1CF9000F007C117D /* Sources */,\n\t\t\t\t97C146EB1CF9000F007C117D /* Frameworks */,\n\t\t\t\t97C146EC1CF9000F007C117D /* Resources */,\n\t\t\t\t9705A1C41CF9048500538489 /* Embed Frameworks */,\n\t\t\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */,\n\t\t\t\tA16AE44BD745529DCD8AAD30 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Runner;\n\t\t\tproductName = Runner;\n\t\t\tproductReference = 97C146EE1CF9000F007C117D /* Runner.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t97C146E61CF9000F007C117D /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1020;\n\t\t\t\tORGANIZATIONNAME = \"\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t97C146ED1CF9000F007C117D = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 7.3.1;\n\t\t\t\t\t\tLastSwiftMigration = 1100;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 97C146E51CF9000F007C117D;\n\t\t\tproductRefGroup = 97C146EF1CF9000F007C117D /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t97C146ED1CF9000F007C117D /* Runner */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t97C146EC1CF9000F007C117D /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,\n\t\t\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,\n\t\t\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Thin Binary\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" embed_and_thin\";\n\t\t};\n\t\t504AAE8408988510F90F1B85 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"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\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t9740EEB61CF901F6004384FC /* Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" build\";\n\t\t};\n\t\tA16AE44BD745529DCD8AAD30 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t97C146EA1CF9000F007C117D /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,\n\t\t\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t97C146FA1CF9000F007C117D /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FB1CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C147001CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t249021D3217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t249021D4217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.example;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t97C147031CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147041CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Owholemodule\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t97C147061CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.example;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147071CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/Flutter\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.example;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147031CF9000F007C117D /* Debug */,\n\t\t\t\t97C147041CF9000F007C117D /* Release */,\n\t\t\t\t249021D3217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147061CF9000F007C117D /* Debug */,\n\t\t\t\t97C147071CF9000F007C117D /* Release */,\n\t\t\t\t249021D4217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 97C146E61CF9000F007C117D /* Project object */;\n}\n"
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1020\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n               BuildableName = \"Runner.app\"\n               BlueprintName = \"Runner\"\n               ReferencedContainer = \"container:Runner.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n      <AdditionalOptions>\n      </AdditionalOptions>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Profile\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "example/ios/Runner.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/lib/generated/i18n.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\n\n// ignore_for_file: non_constant_identifier_names\n// ignore_for_file: camel_case_types\n// ignore_for_file: prefer_single_quotes\n\n// This file is automatically generated. DO NOT EDIT, all your changes would be lost.\nclass S implements WidgetsLocalizations {\n  const S();\n\n  static S current;\n\n  static const GeneratedLocalizationsDelegate delegate =\n    GeneratedLocalizationsDelegate();\n\n  static S of(BuildContext context) => Localizations.of<S>(context, S);\n\n  @override\n  TextDirection get textDirection => TextDirection.ltr;\n\n}\n\nclass $en extends S {\n  const $en();\n}\n\nclass GeneratedLocalizationsDelegate extends LocalizationsDelegate<S> {\n  const GeneratedLocalizationsDelegate();\n\n  List<Locale> get supportedLocales {\n    return const <Locale>[\n      Locale(\"en\", \"\"),\n    ];\n  }\n\n  LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) {\n    return (List<Locale> locales, Iterable<Locale> supported) {\n      if (locales == null || locales.isEmpty) {\n        return fallback ?? supported.first;\n      } else {\n        return _resolve(locales.first, fallback, supported, withCountry);\n      }\n    };\n  }\n\n  LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) {\n    return (Locale locale, Iterable<Locale> supported) {\n      return _resolve(locale, fallback, supported, withCountry);\n    };\n  }\n\n  @override\n  Future<S> load(Locale locale) {\n    final String lang = getLang(locale);\n    if (lang != null) {\n      switch (lang) {\n        case \"en\":\n          S.current = const $en();\n          return SynchronousFuture<S>(S.current);\n        default:\n          // NO-OP.\n      }\n    }\n    S.current = const S();\n    return SynchronousFuture<S>(S.current);\n  }\n\n  @override\n  bool isSupported(Locale locale) => _isSupported(locale, true);\n\n  @override\n  bool shouldReload(GeneratedLocalizationsDelegate old) => false;\n\n  ///\n  /// Internal method to resolve a locale from a list of locales.\n  ///\n  Locale _resolve(Locale locale, Locale fallback, Iterable<Locale> supported, bool withCountry) {\n    if (locale == null || !_isSupported(locale, withCountry)) {\n      return fallback ?? supported.first;\n    }\n\n    final Locale languageLocale = Locale(locale.languageCode, \"\");\n    if (supported.contains(locale)) {\n      return locale;\n    } else if (supported.contains(languageLocale)) {\n      return languageLocale;\n    } else {\n      final Locale fallbackLocale = fallback ?? supported.first;\n      return fallbackLocale;\n    }\n  }\n\n  ///\n  /// Returns true if the specified locale is supported, false otherwise.\n  ///\n  bool _isSupported(Locale locale, bool withCountry) {\n    if (locale != null) {\n      for (Locale supportedLocale in supportedLocales) {\n        // Language must always match both locales.\n        if (supportedLocale.languageCode != locale.languageCode) {\n          continue;\n        }\n\n        // If country code matches, return this locale.\n        if (supportedLocale.countryCode == locale.countryCode) {\n          return true;\n        }\n\n        // If no country requirement is requested, check if this locale has no country.\n        if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) {\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n}\n\nString getLang(Locale l) => l == null\n  ? null\n  : l.countryCode != null && l.countryCode.isEmpty\n    ? l.languageCode\n    : l.toString();\n"
  },
  {
    "path": "example/lib/main.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record_example/path_provider_screen.dart';\nimport 'package:flutter_plugin_record_example/record_mp3_screen.dart';\nimport 'package:flutter_plugin_record_example/record_screen.dart';\nimport 'package:flutter_plugin_record_example/wechat_record_screen.dart';\n\nvoid main() => runApp(MyApp());\n\nclass MyApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return new MaterialApp(\n      title: 'Flutter Demo',\n      theme: new ThemeData(\n        primarySwatch: Colors.blue,\n      ),\n      home: new MyHomePage(title: 'Flutter Demo Home Page'),\n      routes: {\n        \"RecordScreen\": (BuildContext context) => new RecordScreen(),\n        \"RecordMp3Screen\": (BuildContext context) => new RecordMp3Screen(),\n        \"WeChatRecordScreen\": (BuildContext context) =>\n            new WeChatRecordScreen(),\n        \"PathProviderScreen\": (BuildContext context) =>\n            new PathProviderScreen(),\n      },\n    );\n  }\n}\n\nclass MyHomePage extends StatefulWidget {\n  MyHomePage({Key key, this.title}) : super(key: key);\n  final String title;\n\n  @override\n  _MyHomePageState createState() => new _MyHomePageState();\n}\n\nclass _MyHomePageState extends State<MyHomePage> {\n  @override\n  void initState() {\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return new Scaffold(\n      appBar: new AppBar(\n        title: new Text(\"flutter版微信语音录制实现\"),\n      ),\n      body: new Center(\n        child: new Column(\n          mainAxisSize: MainAxisSize.min,\n          children: <Widget>[\n            new FlatButton(\n                onPressed: () {\n                  Navigator.pushNamed<dynamic>(context, \"RecordScreen\");\n                },\n                child: new Text(\"进入语音录制界面\")),\n            new FlatButton(\n                onPressed: () {\n                  Navigator.pushNamed<dynamic>(context, \"RecordMp3Screen\");\n                },\n                child: new Text(\"进入录制mp3模式\")),\n            new FlatButton(\n                onPressed: () {\n                  Navigator.pushNamed<dynamic>(context, \"WeChatRecordScreen\");\n                },\n                child: new Text(\"进入仿微信录制界面\")),\n            new FlatButton(\n                onPressed: () {\n                  Navigator.pushNamed<dynamic>(context, \"PathProviderScreen\");\n                },\n                child: new Text(\"进入文件路径获取界面\")),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "example/lib/path_provider_screen.dart",
    "content": "import 'dart:io';\n\nimport 'package:flutter/material.dart';\nimport 'package:path_provider/path_provider.dart';\n\nclass PathProviderScreen extends StatefulWidget {\n  PathProviderScreen({Key key, this.title}) : super(key: key);\n  final String title;\n\n  @override\n  _PathProviderScreenState createState() => _PathProviderScreenState();\n}\n\nclass _PathProviderScreenState extends State<PathProviderScreen> {\n  Future<Directory> _tempDirectory;\n  Future<Directory> _appSupportDirectory;\n  Future<Directory> _appLibraryDirectory;\n  Future<Directory> _appDocumentsDirectory;\n  Future<Directory> _externalDocumentsDirectory;\n  Future<List<Directory>> _externalStorageDirectories;\n  Future<List<Directory>> _externalCacheDirectories;\n\n  void _requestTempDirectory() {\n    setState(() {\n      _tempDirectory = getTemporaryDirectory();\n    });\n  }\n\n  Widget _buildDirectory(\n      BuildContext context, AsyncSnapshot<Directory> snapshot) {\n    Text text = const Text('');\n    if (snapshot.connectionState == ConnectionState.done) {\n      if (snapshot.hasError) {\n        text = Text('Error: ${snapshot.error}');\n      } else if (snapshot.hasData) {\n        text = Text('path: ${snapshot.data.path}');\n      } else {\n        text = const Text('path unavailable');\n      }\n    }\n    return Padding(padding: const EdgeInsets.all(16.0), child: text);\n  }\n\n  Widget _buildDirectories(\n      BuildContext context, AsyncSnapshot<List<Directory>> snapshot) {\n    Text text = const Text('');\n    if (snapshot.connectionState == ConnectionState.done) {\n      if (snapshot.hasError) {\n        text = Text('Error: ${snapshot.error}');\n      } else if (snapshot.hasData) {\n        final String combined =\n        snapshot.data.map((Directory d) => d.path).join(', ');\n        text = Text('paths: $combined');\n      } else {\n        text = const Text('path unavailable');\n      }\n    }\n    return Padding(padding: const EdgeInsets.all(16.0), child: text);\n  }\n\n  void _requestAppDocumentsDirectory() {\n    setState(() {\n      _appDocumentsDirectory = getApplicationDocumentsDirectory();\n    });\n  }\n\n  void _requestAppSupportDirectory() {\n    setState(() {\n      _appSupportDirectory = getApplicationSupportDirectory();\n    });\n  }\n\n  void _requestAppLibraryDirectory() {\n    setState(() {\n      _appLibraryDirectory = getLibraryDirectory();\n    });\n  }\n\n  void _requestExternalStorageDirectory() {\n    setState(() {\n      _externalDocumentsDirectory = getExternalStorageDirectory();\n    });\n  }\n\n  void _requestExternalStorageDirectories(StorageDirectory type) {\n    setState(() {\n      _externalStorageDirectories = getExternalStorageDirectories(type: type);\n    });\n  }\n\n  void _requestExternalCacheDirectories() {\n    setState(() {\n      _externalCacheDirectories = getExternalCacheDirectories();\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: Text(\"获取文件路径界面\"),\n      ),\n      body: Center(\n        child: ListView(\n          children: <Widget>[\n            Padding(\n              padding: const EdgeInsets.all(16.0),\n              child: RaisedButton(\n                child: const Text('Get Temporary Directory'),\n                onPressed: _requestTempDirectory,\n              ),\n            ),\n            FutureBuilder<Directory>(\n                future: _tempDirectory, builder: _buildDirectory),\n            Padding(\n              padding: const EdgeInsets.all(16.0),\n              child: RaisedButton(\n                child: const Text('Get Application Documents Directory'),\n                onPressed: _requestAppDocumentsDirectory,\n              ),\n            ),\n            FutureBuilder<Directory>(\n                future: _appDocumentsDirectory, builder: _buildDirectory),\n            Padding(\n              padding: const EdgeInsets.all(16.0),\n              child: RaisedButton(\n                child: const Text('Get Application Support Directory'),\n                onPressed: _requestAppSupportDirectory,\n              ),\n            ),\n            FutureBuilder<Directory>(\n                future: _appSupportDirectory, builder: _buildDirectory),\n            Padding(\n              padding: const EdgeInsets.all(16.0),\n              child: RaisedButton(\n                child: const Text('Get Application Library Directory'),\n                onPressed: _requestAppLibraryDirectory,\n              ),\n            ),\n            FutureBuilder<Directory>(\n                future: _appLibraryDirectory, builder: _buildDirectory),\n            Padding(\n              padding: const EdgeInsets.all(16.0),\n              child: RaisedButton(\n                child: Text(\n                    '${Platform.isIOS ? \"External directories are unavailable \" \"on iOS\" : \"Get External Storage Directory\"}'),\n                onPressed:\n                Platform.isIOS ? null : _requestExternalStorageDirectory,\n              ),\n            ),\n            FutureBuilder<Directory>(\n                future: _externalDocumentsDirectory, builder: _buildDirectory),\n            Column(children: <Widget>[\n              Padding(\n                padding: const EdgeInsets.all(16.0),\n                child: RaisedButton(\n                  child: Text(\n                      '${Platform.isIOS ? \"External directories are unavailable \" \"on iOS\" : \"Get External Storage Directories\"}'),\n                  onPressed: Platform.isIOS\n                      ? null\n                      : () {\n                    _requestExternalStorageDirectories(\n                      StorageDirectory.music,\n                    );\n                  },\n                ),\n              ),\n            ]),\n            FutureBuilder<List<Directory>>(\n                future: _externalStorageDirectories,\n                builder: _buildDirectories),\n            Column(children: <Widget>[\n              Padding(\n                padding: const EdgeInsets.all(16.0),\n                child: RaisedButton(\n                  child: Text(\n                      '${Platform.isIOS ? \"External directories are unavailable \" \"on iOS\" : \"Get External Cache Directories\"}'),\n                  onPressed:\n                  Platform.isIOS ? null : _requestExternalCacheDirectories,\n                ),\n              ),\n            ]),\n            FutureBuilder<List<Directory>>(\n                future: _externalCacheDirectories, builder: _buildDirectories),\n          ],\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "example/lib/record_mp3_screen.dart",
    "content": "import 'dart:io';\n\nimport 'package:flustars/flustars.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/flutter_plugin_record.dart';\nimport 'package:path_provider/path_provider.dart';\n\nclass RecordMp3Screen extends StatefulWidget {\n  @override\n  _RecordMp3ScreenState createState() => _RecordMp3ScreenState();\n}\n\nclass _RecordMp3ScreenState extends State<RecordMp3Screen> {\n  FlutterPluginRecord recordPlugin = new FlutterPluginRecord();\n\n  String filePath = \"\";\n\n  @override\n  void initState() {\n    super.initState();\n\n    ///初始化方法的监听\n    recordPlugin.responseFromInit.listen((data) {\n      if (data) {\n        print(\"初始化成功\");\n      } else {\n        print(\"初始化失败\");\n      }\n    });\n\n    /// 开始录制或结束录制的监听\n    recordPlugin.response.listen((data) {\n      if (data.msg == \"onStop\") {\n        ///结束录制时会返回录制文件的地址方便上传服务器\n        print(\"onStop  文件路径\" + data.path);\n        filePath = data.path;\n        print(\"onStop  时长 \" + data.audioTimeLength.toString());\n      } else if (data.msg == \"onStart\") {\n        print(\"onStart --\");\n      } else {\n        print(\"--\" + data.msg);\n      }\n    });\n\n    ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式\n    recordPlugin.responseFromAmplitude.listen((data) {\n      var voiceData = double.parse(data.msg);\n      print(\"振幅大小   \" + voiceData.toString());\n    });\n\n    recordPlugin.responsePlayStateController.listen((data) {\n      print(\"播放路径   \" + data.playPath);\n      print(\"播放状态   \" + data.playState);\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('录制mp3'),\n      ),\n      body: Center(\n        child: Column(\n          children: <Widget>[\n            FlatButton(\n              child: Text(\"初始化录制mp3\"),\n              onPressed: () {\n                _initRecordMp3();\n              },\n            ),\n            FlatButton(\n              child: Text(\"开始录制\"),\n              onPressed: () {\n                start();\n              },\n            ),\n            FlatButton(\n              child: Text(\"根据路径录制mp3文件\"),\n              onPressed: () {\n                _requestAppDocumentsDirectory();\n              },\n            ),\n            FlatButton(\n              child: Text(\"停止录制\"),\n              onPressed: () {\n                stop();\n              },\n            ),\n            FlatButton(\n              child: Text(\"播放\"),\n              onPressed: () {\n                play();\n              },\n            ),\n            FlatButton(\n              child: Text(\"播放本地指定路径录音文件\"),\n              onPressed: () {\n                playByPath(filePath,\"file\");\n              },\n            ),\n            FlatButton(\n              child: Text(\"播放网络mp3文件\"),\n              onPressed: () {\n                playByPath(\"https://test-1259809289.cos.ap-nanjing.myqcloud.com/temp.mp3\",\"url\");\n              },\n            ),\n            FlatButton(\n              child: Text(\"暂停|继续播放\"),\n              onPressed: () {\n                pause();\n              },\n            ),\n            FlatButton(\n              child: Text(\"停止播放\"),\n              onPressed: () {\n                stopPlay();\n              },\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n\n  void _requestAppDocumentsDirectory() {\n//    if(Platform.isIOS){\n//      //ios相关代码\n//      setState(() {\n//        getApplicationDocumentsDirectory().then((value) {\n//          String nowDataTimeStr = DateUtil.getNowDateMs().toString();\n//          String wavPath = value.path + \"/\" + nowDataTimeStr + \".wav\";\n//          startByWavPath(wavPath);\n//        });\n//      });\n//    }else if(Platform.isAndroid){\n//      //android相关代码\n//    }\n\n    setState(() {\n      getApplicationDocumentsDirectory().then((value) {\n        String nowDataTimeStr = DateUtil.getNowDateMs().toString();\n        // TODO  注意IOS 传递的Mp3路径一定是以 .MP3 结尾\n        String wavPath =\"\";\n        if (Platform.isIOS) {\n           wavPath = value.path + \"/\" + nowDataTimeStr+\".MP3\";\n        }else{\n           wavPath = value.path + \"/\" + nowDataTimeStr;\n        }\n        startByWavPath(wavPath);\n      });\n    });\n  }\n\n  ///初始化语音录制的方法\n  void _initRecordMp3() async {\n    recordPlugin.initRecordMp3();\n  }\n\n  ///开始语音录制的方法\n  void start() async {\n    recordPlugin.start();\n  }\n\n  ///根据传递的路径进行语音录制\n  void startByWavPath(String wavPath) async {\n    recordPlugin.startByWavPath(wavPath);\n  }\n\n  ///停止语音录制的方法\n  void stop() {\n    recordPlugin.stop();\n  }\n\n  ///播放语音的方法\n  void play() {\n    recordPlugin.play();\n  }\n\n  ///播放指定路径录音文件  url为iOS播放网络语音，file为播放本地语音文件\n  void playByPath(String path,String type) {\n    recordPlugin.playByPath(path,type);\n  }\n\n  ///暂停|继续播放\n  void pause() {\n    recordPlugin.pausePlay();\n  }\n\n  @override\n  void dispose() {\n    /// 当界面退出的时候是释放录音资源\n    recordPlugin.dispose();\n    super.dispose();\n  }\n\n  void stopPlay() {\n    recordPlugin.stopPlay();\n  }\n}\n"
  },
  {
    "path": "example/lib/record_screen.dart",
    "content": "import 'dart:io';\n\nimport 'package:flustars/flustars.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/flutter_plugin_record.dart';\nimport 'package:path_provider/path_provider.dart';\n\nclass RecordScreen extends StatefulWidget {\n  @override\n  _RecordScreenState createState() => _RecordScreenState();\n}\n\nclass _RecordScreenState extends State<RecordScreen> {\n  FlutterPluginRecord recordPlugin = new FlutterPluginRecord();\n\n  String filePath = \"\";\n\n  @override\n  void initState() {\n    super.initState();\n\n    ///初始化方法的监听\n    recordPlugin.responseFromInit.listen((data) {\n      if (data) {\n        print(\"初始化成功\");\n      } else {\n        print(\"初始化失败\");\n      }\n    });\n\n    /// 开始录制或结束录制的监听\n    recordPlugin.response.listen((data) {\n      if (data.msg == \"onStop\") {\n        ///结束录制时会返回录制文件的地址方便上传服务器\n        print(\"onStop  文件路径\" + data.path);\n        filePath = data.path;\n        print(\"onStop  时长 \" + data.audioTimeLength.toString());\n      } else if (data.msg == \"onStart\") {\n        print(\"onStart --\");\n      } else {\n        print(\"--\" + data.msg);\n      }\n    });\n\n    ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式\n    recordPlugin.responseFromAmplitude.listen((data) {\n      var voiceData = double.parse(data.msg);\n      print(\"振幅大小   \" + voiceData.toString());\n    });\n\n    recordPlugin.responsePlayStateController.listen((data) {\n      print(\"播放路径   \" + data.playPath);\n      print(\"播放状态   \" + data.playState);\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('录制wav'),\n      ),\n      body: Center(\n        child: Column(\n          children: <Widget>[\n            FlatButton(\n              child: Text(\"初始化\"),\n              onPressed: () {\n                _init();\n              },\n            ),\n            FlatButton(\n              child: Text(\"开始录制\"),\n              onPressed: () {\n                start();\n              },\n            ),\n            FlatButton(\n              child: Text(\"根据路径录制wav文件\"),\n              onPressed: () {\n                _requestAppDocumentsDirectory();\n              },\n            ),\n            FlatButton(\n              child: Text(\"停止录制\"),\n              onPressed: () {\n                stop();\n              },\n            ),\n            FlatButton(\n              child: Text(\"播放\"),\n              onPressed: () {\n                play();\n              },\n            ),\n            FlatButton(\n              child: Text(\"播放本地指定路径录音文件\"),\n              onPressed: () {\n                playByPath(filePath,\"file\");\n              },\n            ),\n            FlatButton(\n              child: Text(\"播放网络wav文件\"),\n              onPressed: () {\n                playByPath(\"https://test-1259809289.cos.ap-nanjing.myqcloud.com/test.wav\",\"url\");\n              },\n            ),\n            FlatButton(\n              child: Text(\"暂停|继续播放\"),\n              onPressed: () {\n                pause();\n              },\n            ),\n            FlatButton(\n              child: Text(\"停止播放\"),\n              onPressed: () {\n                stopPlay();\n              },\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n\n  void _requestAppDocumentsDirectory() {\n//    if(Platform.isIOS){\n//      //ios相关代码\n//      setState(() {\n//        getApplicationDocumentsDirectory().then((value) {\n//          String nowDataTimeStr = DateUtil.getNowDateMs().toString();\n//          String wavPath = value.path + \"/\" + nowDataTimeStr + \".wav\";\n//          startByWavPath(wavPath);\n//        });\n//      });\n//    }else if(Platform.isAndroid){\n//      //android相关代码\n//    }\n\n    setState(() {\n      getApplicationDocumentsDirectory().then((value) {\n        String nowDataTimeStr = DateUtil.getNowDateMs().toString();\n        String wavPath = value.path + \"/\" + nowDataTimeStr + \".wav\";\n        print(wavPath);\n        startByWavPath(wavPath);\n      });\n    });\n  }\n\n  ///初始化语音录制的方法\n  void _init() async {\n    recordPlugin.init();\n  }\n\n  ///开始语音录制的方法\n  void start() async {\n    recordPlugin.start();\n  }\n\n  ///根据传递的路径进行语音录制\n  void startByWavPath(String wavPath) async {\n    recordPlugin.startByWavPath(wavPath);\n  }\n\n  ///停止语音录制的方法\n  void stop() {\n    recordPlugin.stop();\n  }\n\n  ///播放语音的方法\n  void play() {\n    recordPlugin.play();\n  }\n\n  ///播放指定路径录音文件  url为iOS播放网络语音，file为播放本地语音文件\n  void playByPath(String path,String type) {\n    recordPlugin.playByPath(path,type);\n  }\n\n  ///暂停|继续播放\n  void pause() {\n    recordPlugin.pausePlay();\n  }\n\n  @override\n  void dispose() {\n    /// 当界面退出的时候是释放录音资源\n    recordPlugin.dispose();\n    super.dispose();\n  }\n\n  void stopPlay() {\n    recordPlugin.stopPlay();\n  }\n}\n"
  },
  {
    "path": "example/lib/wechat_record_screen.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/index.dart';\n\nclass WeChatRecordScreen extends StatefulWidget {\n  @override\n  _WeChatRecordScreenState createState() => _WeChatRecordScreenState();\n}\n\nclass _WeChatRecordScreenState extends State<WeChatRecordScreen> {\n  String toastShow = \"悬浮框\";\n  OverlayEntry overlayEntry;\n\n  showView(BuildContext context) {\n    if (overlayEntry == null) {\n      overlayEntry = new OverlayEntry(builder: (content) {\n        return Positioned(\n          top: MediaQuery.of(context).size.height * 0.5 - 80,\n          left: MediaQuery.of(context).size.width * 0.5 - 80,\n          child: Material(\n            child: Center(\n              child: Opacity(\n                opacity: 0.8,\n                child: Container(\n                  width: 100,\n                  height: 100,\n                  decoration: BoxDecoration(\n                    color: Color(0xff77797A),\n                    borderRadius: BorderRadius.all(Radius.circular(20.0)),\n                  ),\n                  child: Column(\n                    children: <Widget>[\n                      Container(\n//                      padding: EdgeInsets.only(right: 20, left: 20, top: 0),\n                        child: Text(\n                          toastShow,\n                          style: TextStyle(\n                            fontStyle: FontStyle.normal,\n                            color: Colors.white,\n                            fontSize: 14,\n                          ),\n                        ),\n                      )\n                    ],\n                  ),\n                ),\n              ),\n            ),\n          ),\n        );\n      });\n      Overlay.of(context).insert(overlayEntry);\n    }\n  }\n\n  startRecord() {\n    print(\"开始录制\");\n  }\n\n  stopRecord(String path, double audioTimeLength) {\n    print(\"结束束录制\");\n    print(\"音频文件位置\" + path);\n    print(\"音频录制时长\" + audioTimeLength.toString());\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: Text(\"仿微信发送语音\"),\n      ),\n      body: Container(\n        child: Column(\n          children: <Widget>[\n            new FlatButton(\n                onPressed: () {\n                  showView(context);\n                },\n                child: new Text(\"悬浮组件\")),\n            new FlatButton(\n                onPressed: () {\n                  if (overlayEntry != null) {\n                    overlayEntry.remove();\n                    overlayEntry = null;\n                  }\n                },\n                child: new Text(\"隐藏悬浮组件\")),\n            new FlatButton(\n                onPressed: () {\n                  setState(() {\n                    toastShow = \"111\";\n                    if (overlayEntry != null) {\n                      overlayEntry.markNeedsBuild();\n                    }\n                  });\n                },\n                child: new Text(\"悬浮窗状态更新\")),\n            new VoiceWidget(\n              startRecord: startRecord,\n              stopRecord: stopRecord,\n              // 加入定制化Container的相关属性\n              height: 40.0,\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "example/pubspec.yaml",
    "content": "name: flutter_plugin_record_example\ndescription: The flutter voice recording plug-in,provides the recording animation and the recording successfully returns to the recording file path\n\n# The following line prevents the package from being accidentally published to\n# pub.dev using `pub publish`. This is preferred for private packages.\npublish_to: 'none' # Remove this line if you wish to publish to pub.dev\n\n# The following defines the version and build number for your application.\n# A version number is three numbers separated by dots, like 1.2.43\n# followed by an optional build number separated by a +.\n# Both the version and the builder number may be overridden in flutter\n# build by specifying --build-name and --build-number, respectively.\n# In Android, build-name is used as versionName while build-number used as versionCode.\n# Read more about Android versioning at https://developer.android.com/studio/publish/versioning\n# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.\n# Read more about iOS versioning at\n# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html\nversion: 1.0.0+1\n\nenvironment:\n  sdk: \">=2.7.0 <3.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n\n\n  # The following adds the Cupertino Icons font to your application.\n  # Use with the CupertinoIcons class for iOS style icons.\n  cupertino_icons: ^0.1.3\n  path_provider: ^1.6.8\n  flustars: ^0.3.2\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n    \n  flutter_plugin_record:\n    path: ../\n\n# For information on the generic Dart part of this file, see the\n# following page: https://dart.dev/tools/pub/pubspec\n\n# The following section is specific to Flutter.\nflutter:\n\n  # The following line ensures that the Material Icons font is\n  # included with your application, so that you can use the icons in\n  # the material Icons class.\n  uses-material-design: true\n\n  # To add assets to your application, add an assets section, like this:\n  # assets:\n  #   - images/a_dot_burr.jpeg\n  #   - images/a_dot_ham.jpeg\n\n  # An image asset can refer to one or more resolution-specific \"variants\", see\n  # https://flutter.dev/assets-and-images/#resolution-aware.\n\n  # For details regarding adding assets from package dependencies, see\n  # https://flutter.dev/assets-and-images/#from-packages\n\n  # To add custom fonts to your application, add a fonts section here,\n  # in this \"flutter\" section. Each entry in this list should have a\n  # \"family\" key with the font family name, and a \"fonts\" key with a\n  # list giving the asset and other descriptors for the font. For\n  # example:\n  # fonts:\n  #   - family: Schyler\n  #     fonts:\n  #       - asset: fonts/Schyler-Regular.ttf\n  #       - asset: fonts/Schyler-Italic.ttf\n  #         style: italic\n  #   - family: Trajan Pro\n  #     fonts:\n  #       - asset: fonts/TrajanPro.ttf\n  #       - asset: fonts/TrajanPro_Bold.ttf\n  #         weight: 700\n  #\n  # For details regarding fonts from package dependencies,\n  # see https://flutter.dev/custom-fonts/#from-packages\n"
  },
  {
    "path": "example/test/widget_test.dart",
    "content": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester\n// utility that Flutter provides. For example, you can send tap and scroll\n// gestures. You can also use WidgetTester to find child widgets in the widget\n// tree, read text, and verify that the values of widget properties are correct.\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nimport 'package:flutter_plugin_record_example/main.dart';\n\nvoid main() {\n  testWidgets('Verify Platform version', (WidgetTester tester) async {\n    // Build our app and trigger a frame.\n    await tester.pumpWidget(MyApp());\n\n    // Verify that platform version is retrieved.\n    expect(\n      find.byWidgetPredicate(\n        (Widget widget) => widget is Text &&\n                           widget.data.startsWith('Running on:'),\n      ),\n      findsOneWidget,\n    );\n  });\n}\n"
  },
  {
    "path": "flutter_plugin_record.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\" inherit-compiler-output=\"true\">\n    <exclude-output />\n    <content url=\"file://$MODULE_DIR$\">\n      <sourceFolder url=\"file://$MODULE_DIR$/lib\" isTestSource=\"false\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/.dart_tool\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/.idea\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/.pub\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/build\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/.dart_tool\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/.pub\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/build\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_plugin_record/.dart_tool\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_plugin_record/.pub\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_plugin_record/build\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_plugin_record/example/.dart_tool\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_plugin_record/example/.pub\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_plugin_record/example/build\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/example/ios/Flutter/App.framework/flutter_assets/packages\" />\n    </content>\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n    <orderEntry type=\"library\" name=\"Dart SDK\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"Flutter Plugins\" level=\"project\" />\n  </component>\n</module>"
  },
  {
    "path": "ios/.gitignore",
    "content": ".idea/\n.vagrant/\n.sconsign.dblite\n.svn/\n\n.DS_Store\n*.swp\nprofile\n\nDerivedData/\nbuild/\nGeneratedPluginRegistrant.h\nGeneratedPluginRegistrant.m\n\n.generated/\n\n*.pbxuser\n*.mode1v3\n*.mode2v3\n*.perspectivev3\n\n!default.pbxuser\n!default.mode1v3\n!default.mode2v3\n!default.perspectivev3\n\nxcuserdata\n\n*.moved-aside\n\n*.pyc\n*sync/\nIcon?\n.tags*\n\n/Flutter/Generated.xcconfig\n/Flutter/flutter_export_environment.sh"
  },
  {
    "path": "ios/Assets/.gitkeep",
    "content": ""
  },
  {
    "path": "ios/Classes/DPAudioPlayer.h",
    "content": "\n#import <Foundation/Foundation.h>\n\ntypedef void(^PlayCompleteBlock)(void);\n\ntypedef void(^StartPlayingBlock)(BOOL isPlaying);\n\n@interface DPAudioPlayer : NSObject\n\n/**\n 播放完成回调\n */\n@property (nonatomic, copy) PlayCompleteBlock playComplete;\n\n/**\n 开始播放回调\n */\n@property (nonatomic, copy) StartPlayingBlock startPlaying;\n\n+ (DPAudioPlayer *)sharedInstance;\n\n/**\n 播放data格式录音\n\n @param data 录音data\n */\n- (void)startPlayWithData:(NSData *)data;\n\n/**\n 停止播放\n */\n- (void)stopPlaying;\n\n/// 暂停播放\n- (bool)pausePlaying;\n\n\n\n@end\n"
  },
  {
    "path": "ios/Classes/DPAudioPlayer.m",
    "content": "\n#import \"DPAudioPlayer.h\"\n#import <AVFoundation/AVFoundation.h>\n#import <UIKit/UIKit.h>\n\n@interface DPAudioPlayer () <AVAudioPlayerDelegate>\n{\n    BOOL isPlaying;\n}\n\n@property (nonatomic, strong) AVAudioPlayer *audioPlayer;\n\n@end\n\n@implementation DPAudioPlayer\n\nstatic DPAudioPlayer *playerManager = nil;\n+ (DPAudioPlayer *)sharedInstance\n{\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken,^{\n        playerManager = [[DPAudioPlayer alloc] init];\n    });\n    return playerManager;\n}\n\n- (instancetype)init\n{\n    if (self) {\n        //创建缓存录音文件到Tmp\n        NSString *wavPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@\"WAVtemporaryPlayer.wav\"];\n        NSString *amrPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@\"AMRtemporaryPlayer.amr\"];\n        \n        if (![[NSFileManager defaultManager]fileExistsAtPath:wavPlayerFilePath]) {\n            [[NSData data] writeToFile:wavPlayerFilePath atomically:YES];\n        }\n        if (![[NSFileManager defaultManager]fileExistsAtPath:amrPlayerFilePath]) {\n            [[NSData data] writeToFile:amrPlayerFilePath atomically:YES];\n        }\n        \n        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(proximityStateDidChange) name:UIDeviceProximityStateDidChangeNotification object:nil];\n        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];\n        [[AVAudioSession sharedInstance] setActive:YES error:nil];\n\n    }\n    return self;\n}\n\n- (void)startPlayWithData:(NSData *)data\n{\n//    if (isPlaying) return;\n    //打开红外传感器\n    [[UIDevice currentDevice] setProximityMonitoringEnabled:YES];\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    [session setActive:true error:nil];\n    [session setCategory:AVAudioSessionCategoryPlayback error:nil];\n//    //默认情况下扬声器播放\n//    AVAudioSessionPortOverride portOverride = AVAudioSessionPortOverrideNone;\n//    [[AVAudioSession sharedInstance] overrideOutputAudioPort:portOverride error:nil];\n    \n    //self.audioPlayer = [[AVAudioPlayer alloc]initWithData:[self conversionAMRDataToWAVData:data] error:nil];\n    \n    if (isPlaying){\n        [self.audioPlayer stop];\n        self.audioPlayer = nil;\n        isPlaying = NO;\n    }\n    self.audioPlayer = [[AVAudioPlayer alloc]initWithData:data error:nil];\n    self.audioPlayer.meteringEnabled = YES;\n    self.audioPlayer.delegate = self;\n    self.audioPlayer.volume = 1.0;\n    self.audioPlayer.numberOfLoops = 0;\n    [self.audioPlayer prepareToPlay];\n    [self.audioPlayer play];\n    \n    if ([self.audioPlayer isPlaying]) {\n        isPlaying = YES;\n        if (self.startPlaying) {\n            self.startPlaying(YES);\n        }\n    } else {\n        isPlaying = NO;\n        if (self.startPlaying) {\n            self.startPlaying(NO);\n        }\n    }\n}\n\n//暂停播放\n- (bool)pausePlaying\n{\n    if (isPlaying){\n        //关闭红外传感器\n        [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];\n        [self.audioPlayer pause];\n        isPlaying = NO;\n    }else{\n        [self.audioPlayer play];\n        isPlaying = YES;\n    }\n   \n    return isPlaying;\n    \n}\n\n\n- (void)stopPlaying\n{\n    if (!isPlaying) return;\n    //关闭红外传感器\n    [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];\n    [self.audioPlayer stop];\n    self.audioPlayer = nil;\n    isPlaying = NO;\n}\n\n- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag\n{\n    if (flag) {\n        [self stopPlaying];\n        if (self.playComplete) {\n            self.playComplete();\n        }\n    }\n}\n\n- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer*)player error:(NSError *)error{\n    //解码错误执行的动作\n    NSLog(@\"\");\n}\n\n//- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player\n//{\n//    isPlaying = NO;\n//    [player stop];\n//}\n\n////转换amr文件类型data为wav文件类型data\n//- (NSData *)conversionAMRDataToWAVData:(NSData *)amrData\n//{\n//    \n//    NSString *wavPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@\"WAVtemporaryPlayer.wav\"];\n//    NSString *amrPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@\"AMRtemporaryPlayer.amr\"];\n//    \n//    //amr的data写入文件\n//    [amrData writeToFile:amrPlayerFilePath atomically:YES];\n//    //将AMR文件转码成WAVE文件\n//    amr_file_to_wave_file([amrPlayerFilePath cStringUsingEncoding:NSUTF8StringEncoding],\n//                          [wavPlayerFilePath cStringUsingEncoding:NSUTF8StringEncoding]);\n//\n//    //得到转码后wav的data\n//    return [NSData dataWithContentsOfFile:wavPlayerFilePath];\n//}\n\n- (void)proximityStateDidChange\n{\n    if ([UIDevice currentDevice].proximityState) {\n        NSLog(@\"有物品靠近\");\n        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];\n    } else {\n        NSLog(@\"有物品离开\");\n        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];\n    }\n}\n\n\n- (void)dealloc\n{\n    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceProximityStateDidChangeNotification object:nil];\n    [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];\n}\n\n@end\n"
  },
  {
    "path": "ios/Classes/DPAudioRecorder.h",
    "content": "\n#import <Foundation/Foundation.h>\n\ntypedef void(^AudioRecorderFinishRecordingBlock)(NSData *data, NSTimeInterval audioTimeLength,NSString *path);\n\ntypedef void(^AudioStartRecordingBlock)(BOOL isRecording);\n\ntypedef void(^AudioRecordingFailBlock)(NSString *reason);\n\ntypedef void(^AudioSpeakPowerBlock)(float power);\n\n/// 录制语音\n@interface DPAudioRecorder : NSObject\n\n/// 录制完成的回调\n@property (nonatomic, copy) AudioRecorderFinishRecordingBlock audioRecorderFinishRecording;\n/// 开始录制回调\n@property (nonatomic, copy) AudioStartRecordingBlock audioStartRecording;\n/// 录制失败回调\n@property (nonatomic, copy) AudioRecordingFailBlock audioRecordingFail;\n/// 音频值测量回调\n@property (nonatomic, copy) AudioSpeakPowerBlock audioSpeakPower;\n\n\n+ (DPAudioRecorder *)sharedInstance;\n\n/// 传递录制文件路径\n- (void)initByWavPath:(NSString*) wavPath;\n- (void)initByMp3;\n\n/// 开始录音方法\n- (void)startRecording;\n\n/// 停止录音方法\n- (void)stopRecording;\n\n@end\n"
  },
  {
    "path": "ios/Classes/DPAudioRecorder.m",
    "content": "\n#import \"DPAudioRecorder.h\"\n#import \"DPAudioPlayer.h\"\n#import <AVFoundation/AVFoundation.h>\n#import <UIKit/UIKit.h>\n#import \"JX_GCDTimerManager.h\"\n\n\n\n#define MAX_RECORDER_TIME 2100  //最大录制时间\n#define MIN_RECORDER_TIME 1    // 最小录制时间\n\n#define TimerName @\"audioTimer_999\"\n\n//定义音频枚举类型\ntypedef NS_ENUM(NSUInteger, CSVoiceType) {\n    CSVoiceTypeWav,\n    CSVoiceTypeAmr\n};\n\nstatic const CSVoiceType preferredVoiceType = CSVoiceTypeWav;\n\n\n@interface DPAudioRecorder () <AVAudioRecorderDelegate>\n{\n    BOOL isRecording;\n    dispatch_source_t timer;\n    NSTimeInterval __block audioTimeLength; //录音时长\n}\n@property (nonatomic, strong) AVAudioRecorder *audioRecorder;\n@property (nonatomic, strong) NSString *originWaveFilePath;\n@end\n\n@implementation DPAudioRecorder\n\nstatic DPAudioRecorder *recorderManager = nil;\n\n\n+ (DPAudioRecorder *)sharedInstance\n{\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken,^{\n        recorderManager = [[DPAudioRecorder alloc] init];\n    });\n    \n    return recorderManager;\n}\n\n\n/// 默认构造方法\n- (instancetype)init\n{\n    if (self = [super init]) {\n        //创建缓存录音文件到Tmp\n        NSString *wavRecordFilePath = [self createWaveFilePath];\n        if (![[NSFileManager defaultManager] fileExistsAtPath:wavRecordFilePath]) {\n            [[NSData data] writeToFile:wavRecordFilePath atomically:YES];\n        }\n        self.originWaveFilePath = wavRecordFilePath;\n        \n        NSLog(@\"ios------初始化默认录制文件路径---%@\",wavRecordFilePath);\n    \n    }\n    return self;\n}\n\n\n- (void) initByMp3{\n    //创建缓存录音文件到Tmp\n    NSString *mp3RecordFilePath = [self createMp3FilePath];\n    if (![[NSFileManager defaultManager] fileExistsAtPath:mp3RecordFilePath]) {\n        [[NSData data] writeToFile:mp3RecordFilePath atomically:YES];\n    }\n    self.originWaveFilePath = mp3RecordFilePath;\n    \n    NSLog(@\"ios------初始化录制文件路径---%@\",mp3RecordFilePath);\n\n}\n\n- (NSString *) createMp3FilePath {\n    return [NSTemporaryDirectory() stringByAppendingPathComponent:@\"WAVtemporaryRadio.MP3\"];\n  \n}\n- (NSString *) createWaveFilePath {\n    return [NSTemporaryDirectory() stringByAppendingPathComponent:@\"WAVtemporaryRadio.wav\"];\n  \n}\n\n/// 根据传递过来的文件路径创建wav录制文件路径\n/// @param wavPath 传递的文件路径\n- (void)initByWavPath:(NSString *) wavPath{\n        \n           NSString *wavRecordFilePath = wavPath;\n           if (![[NSFileManager defaultManager] fileExistsAtPath:wavRecordFilePath]) {\n               [[NSData data] writeToFile:wavRecordFilePath atomically:YES];\n           }\n           \n         self.originWaveFilePath = wavRecordFilePath;\n        NSLog(@\"ios-----传递的录制文件路径-------- %@\",wavRecordFilePath);\n}\n\n/// 开始录制方法\n- (void)startRecording\n{\n    if (isRecording) return;\n    \n    [[DPAudioPlayer sharedInstance]stopPlaying];\n    //开始录音\n    [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:nil];\n    //    //默认情况下扬声器播放\n     AVAudioSessionPortOverride portOverride = AVAudioSessionPortOverrideNone;\n    [[AVAudioSession sharedInstance] overrideOutputAudioPort:portOverride error:nil];\n    \n    [[AVAudioSession sharedInstance] setActive:YES error:nil];\n\n    [self.audioRecorder prepareToRecord];\n    \n    [self.audioRecorder record];\n    \n    if ([self.audioRecorder isRecording]) {\n        isRecording = YES;\n        [self activeTimer];\n        if (self.audioStartRecording) {\n            self.audioStartRecording(YES);\n        }\n    } else {\n        if (self.audioStartRecording) {\n            self.audioStartRecording(NO);\n        }\n    }\n    \n    \n    [self createPickSpeakPowerTimer];\n}\n\n- (void)stopRecording;\n{\n    if (!isRecording) return;\n//  try!AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)\n    [self shutDownTimer];\n    [self.audioRecorder stop];\n    self.audioRecorder = nil;\n    \n    //设置播放语音为k公开模式\n    AVAudioSession *avAudioSession = [AVAudioSession sharedInstance];\n    [avAudioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];\n\n}\n\n- (void)activeTimer\n{\n    \n    //录音时长\n    audioTimeLength = 0;\n    NSTimeInterval timeInterval = 0.1;\n    __weak typeof(self) weakSelf = self;\n    [[JX_GCDTimerManager sharedInstance] scheduledDispatchTimerWithName:TimerName timeInterval:timeInterval queue:nil repeats:YES actionOption:AbandonPreviousAction action:^{\n        __strong typeof(weakSelf) strongSelf = weakSelf;\n        strongSelf->audioTimeLength += timeInterval;\n        if (strongSelf->audioTimeLength >= MAX_RECORDER_TIME) { //大于等于 MAX_RECORDER_TIME 秒停止\n            [strongSelf stopRecording];\n        }\n    }];\n}\n\n- (void)shutDownTimer\n{\n    [[JX_GCDTimerManager sharedInstance] cancelAllTimer];//定时器停止\n}\n\n- (AVAudioRecorder *)audioRecorder {\n    if (!_audioRecorder) {\n        \n        //暂存录音文件路径\n        NSString *wavRecordFilePath = self.originWaveFilePath;\n        NSLog(@\"%@\", wavRecordFilePath);\n        NSDictionary *param =\n        @{AVSampleRateKey:@8000.0,    //采样率\n          AVFormatIDKey:@(kAudioFormatLinearPCM),//音频格式\n          AVLinearPCMBitDepthKey:@16,    //采样位数 默认 16\n          AVNumberOfChannelsKey:@1,   // 通道的数目\n          AVEncoderAudioQualityKey:@(AVAudioQualityMin),\n          AVEncoderBitRateKey:@16000,\n//          AVEncoderBitRateStrategyKey:AVAudioBitRateStrategy_VariableConstrained\n          };\n        \n        NSError *initError;\n        NSURL *fileURL = [NSURL fileURLWithPath:wavRecordFilePath];\n        _audioRecorder = [[AVAudioRecorder alloc] initWithURL:fileURL settings:param error:&initError];\n        if (initError) {\n            NSLog(@\"AVAudioRecorder initError:%@\", initError.localizedDescription);\n        }\n        _audioRecorder.delegate = self;\n        _audioRecorder.meteringEnabled = YES;\n    }\n    return _audioRecorder;\n}\n\n#pragma mark - AVAudioRecorder\n\n- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag\n{\n    //暂存录音文件路径\n    NSString *wavRecordFilePath = self.originWaveFilePath;\n    NSLog(@\"录音暂存位置 %@ \",wavRecordFilePath);\n    NSData *cacheAudioData;\n    switch (preferredVoiceType) {\n        case CSVoiceTypeWav:\n            cacheAudioData = [NSData dataWithContentsOfFile:wavRecordFilePath];\n            break;\n    }\n    \n    //大于最小录音时长时,发送数据\n    if (audioTimeLength > MIN_RECORDER_TIME) {\n        dispatch_async(dispatch_get_global_queue(0, 0), ^{\n            NSUInteger location = 4100;\n            NSData *body = [cacheAudioData subdataWithRange:NSMakeRange(location, cacheAudioData.length - location)];\n            NSMutableData *data1 = WriteWavFileHeader(body.length + 44, 8000, 1, 16).mutableCopy;\n            [data1 appendData:body];\n//            NSLog(@\"date1date1date1date1[0-200]:%@\", [data1 subdataWithRange:NSMakeRange(0, 200)]);\n            \n            dispatch_sync(dispatch_get_main_queue(), ^{\n                if (self.audioRecorderFinishRecording) {\n                    self.audioRecorderFinishRecording(data1, self->audioTimeLength,wavRecordFilePath);\n                }\n            });\n        });\n    } else {\n        if (self.audioRecordingFail) {\n            self.audioRecordingFail(@\"录音时长小于设定最短时长\");\n        }\n    }\n    \n    isRecording = NO;\n    \n    //取消定时器\n    if (timer) {\n        dispatch_source_cancel(timer);\n        timer = NULL;\n    }\n}\n\nNSData* WriteWavFileHeader(long lengthWithHeader, int sampleRate, int channels, int PCMBitDepth) {\n    Byte header[44];\n    header[0] = 'R';  // RIFF/WAVE header\n    header[1] = 'I';\n    header[2] = 'F';\n    header[3] = 'F';\n    long totalDataLen = lengthWithHeader - 8;\n    header[4] = (Byte) (totalDataLen & 0xff);  //file-size (equals file-size - 8)\n    header[5] = (Byte) ((totalDataLen >> 8) & 0xff);\n    header[6] = (Byte) ((totalDataLen >> 16) & 0xff);\n    header[7] = (Byte) ((totalDataLen >> 24) & 0xff);\n    header[8] = 'W';  // Mark it as type \"WAVE\"\n    header[9] = 'A';\n    header[10] = 'V';\n    header[11] = 'E';\n    header[12] = 'f';  // Mark the format section 'fmt ' chunk\n    header[13] = 'm';\n    header[14] = 't';\n    header[15] = ' ';\n    header[16] = 16;   // 4 bytes: size of 'fmt ' chunk, Length of format data.  Always 16\n    header[17] = 0;\n    header[18] = 0;\n    header[19] = 0;\n    header[20] = 1;  // format = 1 ,Wave type PCM\n    header[21] = 0;\n    header[22] = (Byte) channels;  // channels\n    header[23] = 0;\n    header[24] = (Byte) (sampleRate & 0xff);\n    header[25] = (Byte) ((sampleRate >> 8) & 0xff);\n    header[26] = (Byte) ((sampleRate >> 16) & 0xff);\n    header[27] = (Byte) ((sampleRate >> 24) & 0xff);\n    int byteRate = sampleRate * channels * PCMBitDepth >> 3;\n    header[28] = (Byte) (byteRate & 0xff);\n    header[29] = (Byte) ((byteRate >> 8) & 0xff);\n    header[30] = (Byte) ((byteRate >> 16) & 0xff);\n    header[31] = (Byte) ((byteRate >> 24) & 0xff);\n    header[32] = (Byte) (channels * PCMBitDepth >> 3); // block align\n    header[33] = 0;\n    header[34] = PCMBitDepth; // bits per sample\n    header[35] = 0;\n    header[36] = 'd'; //\"data\" marker\n    header[37] = 'a';\n    header[38] = 't';\n    header[39] = 'a';\n    long totalAudioLen = lengthWithHeader - 44;\n    header[40] = (Byte) (totalAudioLen & 0xff);  //data-size (equals file-size - 44).\n    header[41] = (Byte) ((totalAudioLen >> 8) & 0xff);\n    header[42] = (Byte) ((totalAudioLen >> 16) & 0xff);\n    header[43] = (Byte) ((totalAudioLen >> 24) & 0xff);\n    return [[NSData alloc] initWithBytes:header length:44];;\n}\n\n//音频值测量\n- (void)createPickSpeakPowerTimer\n{\n    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());\n    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);\n    \n    __weak __typeof(self) weakSelf = self;\n\n    dispatch_source_set_event_handler(timer, ^{\n        __strong __typeof(weakSelf) _self = weakSelf;\n        \n        [_self->_audioRecorder updateMeters];\n        double lowPassResults = pow(10, (0.05 * [_self->_audioRecorder averagePowerForChannel:0]));\n        if (_self.audioSpeakPower) {\n            _self.audioSpeakPower(lowPassResults);\n        }\n    });\n    \n    dispatch_resume(timer);\n}\n\n- (void)dealloc\n{\n    if (isRecording) [self.audioRecorder stop];\n     [[NSNotificationCenter defaultCenter] removeObserver:self];\n}\n\n@end\n"
  },
  {
    "path": "ios/Classes/FlutterPluginRecordPlugin.h",
    "content": "#import <Flutter/Flutter.h>\n\n@interface FlutterPluginRecordPlugin : NSObject<FlutterPlugin>\n@end\n"
  },
  {
    "path": "ios/Classes/FlutterPluginRecordPlugin.m",
    "content": "#import \"FlutterPluginRecordPlugin.h\"\n#import \"DPAudioRecorder.h\"\n#import \"DPAudioPlayer.h\"\n\n\n@implementation FlutterPluginRecordPlugin{\n    FlutterMethodChannel *_channel;\n    FlutterResult  _result;\n    FlutterMethodCall  *_call;\n    NSData  *wavData;\n    NSString *audioPath;\n    BOOL _isInit;//是否执行初始化的标识\n}\n\n+ (void)registerWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar {\n    FlutterMethodChannel *channel = [FlutterMethodChannel\n                                     methodChannelWithName:@\"flutter_plugin_record\"\n                                     binaryMessenger:[registrar messenger]];\n    \n    FlutterPluginRecordPlugin *instance =  [[FlutterPluginRecordPlugin alloc] initWithChannel:channel];\n    [registrar addMethodCallDelegate:instance channel:channel];\n}\n\n- (instancetype)initWithChannel:(FlutterMethodChannel *)channel {\n    self = [super init];\n    if (self) {\n        _channel = channel;\n        _isInit = NO;\n    }\n    return self;\n}\n\n- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{\n    _result  = result;\n    _call = call;\n    NSString *method = call.method;\n    if ([@\"init\" isEqualToString:method]) {\n        [self initRecord ];\n    }else if([@\"initRecordMp3\" isEqualToString:method]){\n        [self initMp3Record];\n    }else if([@\"startByWavPath\" isEqualToString:method]){\n        [self startByWavPath];\n    }else if([@\"start\" isEqualToString:method]){\n        [self start ];\n    }else if([@\"stop\" isEqualToString:method]){\n        [self stop ];\n    }else if([@\"play\" isEqualToString:method]){\n        [self play ];\n    }else if([@\"pause\" isEqualToString:method]){\n        [self pausePlay ];\n    }else if([@\"playByPath\" isEqualToString:method]){\n        [self playByPath];\n    }else if([@\"stopPlay\" isEqualToString:method]){\n        [self stopPlay];\n    }else{\n        result(FlutterMethodNotImplemented);\n    }\n    \n}\n\n\n\n//初始化录制mp3\n- (void) initMp3Record{\n    [DPAudioRecorder.sharedInstance initByMp3];\n    [self initRecord];\n    \n}\n\n///初始化语音录制的方法 初始化录制完成的回调,开始录制的回调,录制失败的回调,录制音量大小的回调\n/// 注意未初始化的话 Flutter 不能监听到上述回调事件\n- (void) initRecord{\n    _isInit = YES;\n    \n    DPAudioRecorder.sharedInstance.audioRecorderFinishRecording = ^void (NSData *data, NSTimeInterval audioTimeLength,NSString *path){\n        self->audioPath =path;\n        self->wavData = data;\n        NSLog(@\"ios  voice   onStop\");\n        NSDictionary *args =   [self->_call arguments];\n        NSString *mId = [args valueForKey:@\"id\"];\n        \n        NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:\n                               @\"success\", @\"result\",\n                               mId, @\"id\",\n                               path, @\"voicePath\",\n                               [NSString stringWithFormat:@\"%.20lf\", audioTimeLength], @\"audioTimeLength\",\n                               nil];\n        [self->_channel invokeMethod:@\"onStop\" arguments:dict3];\n        \n    };\n    \n    DPAudioRecorder.sharedInstance.audioStartRecording =  ^void(BOOL isRecording){\n        NSLog(@\"ios  voice   start  audioStartRecording\");\n    };\n    DPAudioRecorder.sharedInstance.audioRecordingFail = ^void(NSString *reason){\n        \n        NSLog(@\"ios  voice %@\", reason);\n        \n    };\n    DPAudioRecorder.sharedInstance.audioSpeakPower = ^void(float power){\n        NSLog(@\"ios  voice %f\",power);\n        NSString *powerStr = [NSString stringWithFormat:@\"%f\", power];\n        NSDictionary *args =   [self->_call arguments];\n        NSString *mId = [args valueForKey:@\"id\"];\n        NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:\n                               @\"success\",@\"result\",\n                               mId ,@\"id\",\n                               powerStr,@\"amplitude\",\n                               nil];\n        [self->_channel invokeMethod:@\"onAmplitude\" arguments:dict3];\n    };\n    \n    NSLog(@\"ios  voice   init\");\n    NSDictionary *args =   [_call arguments];\n    NSString *mId = [args valueForKey:@\"id\"];\n    NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@\"success\",@\"result\",mId,@\"id\", nil];\n    [_channel invokeMethod:@\"onInit\" arguments:dict3];\n}\n\n\n\n/// 开始录制的方法\n- (void) start{\n    if (!_isInit) {\n        NSLog(@\"ios-------未初始化录制方法- initRecord--\");\n        return;\n    }\n    NSLog(@\"ios--------start record -----function--- start----\");\n    [DPAudioRecorder.sharedInstance startRecording];\n    \n    NSDictionary *args =   [_call arguments];\n    NSString *mId = [args valueForKey:@\"id\"];\n    NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@\"success\",@\"result\",mId,@\"id\", nil];\n    [_channel invokeMethod:@\"onStart\" arguments:dict3];\n}\n\n/// 根据文件路径进行录制\n- (void) startByWavPath{\n    if (!_isInit) {\n        NSLog(@\"ios-------未初始化录制方法- initRecord--\");\n        return;\n    }\n    NSDictionary *args =   [_call arguments];\n    NSString *mId = [args valueForKey:@\"id\"];\n    NSString *wavPath = [args valueForKey:@\"wavPath\"];\n    \n    NSLog(@\"ios--------start record -----function--- startByWavPath----%@\", wavPath);\n    \n    [DPAudioRecorder.sharedInstance initByWavPath:wavPath];\n    [DPAudioRecorder.sharedInstance startRecording];\n    \n    NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@\"success\",@\"result\",mId,@\"id\", nil];\n    [_channel invokeMethod:@\"onStart\" arguments:dict3];\n}\n\n\n\n/// 停止录制的方法\n- (void) stop{\n    if (!_isInit) {\n        NSLog(@\"ios-------未初始化录制方法- initRecord--\");\n        return;\n    }\n    NSLog(@\"ios--------stop record -----function--- stop----\");\n    [DPAudioRecorder.sharedInstance stopRecording];\n}\n\n\n\n///  播放录制完成的音频\n- (void) play{\n    \n    NSLog(@\"ios------play voice by warData----function---play--\");\n    [DPAudioPlayer.sharedInstance startPlayWithData:self->wavData];\n    DPAudioPlayer.sharedInstance.playComplete = ^void(){\n        NSLog(@\"ios-----播放完成----by play\");\n        NSDictionary *args =   [self->_call arguments];\n        NSString *mId = [args valueForKey:@\"id\"];\n        NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:self->audioPath,@\"playPath\",@\"complete\",@\"playState\",mId,@\"id\", nil];\n        [self->_channel invokeMethod:@\"onPlayState\" arguments:dict3];\n    };\n    \n    \n    NSDictionary *args =   [_call arguments];\n    NSString *mId = [args valueForKey:@\"id\"];\n    NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@\"success\",@\"result\",mId,@\"id\", nil];\n    [_channel invokeMethod:@\"onPlay\" arguments:dict3];\n}\n- (void)stopPlay{\n    [DPAudioPlayer.sharedInstance stopPlaying];\n    \n}\n- (void) pausePlay{\n    \n    NSLog(@\"ios------pausePlay----function---pausePlay--\");\n    bool isPlaying =  [DPAudioPlayer.sharedInstance pausePlaying];\n    \n    NSDictionary *args =   [_call arguments];\n    NSString *mId = [args valueForKey:@\"id\"];\n    NSString *isPlayingStr = nil;\n    if (isPlaying) {\n        isPlayingStr = @\"true\";\n    }else{\n        isPlayingStr = @\"false\";\n    }\n    NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:\n                           @\"success\",@\"result\",\n                           isPlayingStr,@\"isPlaying\",\n                           mId,@\"id\",\n                           nil];\n    [_channel invokeMethod:@\"pausePlay\" arguments:dict3];\n}\n\n/// 根据指定路径播放音频\n- (void) playByPath{\n    NSLog(@\"ios------play voice by path-----function---playByPath---\");\n    NSDictionary *args =   [_call arguments];\n    NSString *filePath = [args valueForKey:@\"path\"];\n    \n    NSString *typeStr = [args valueForKey:@\"type\"];\n    NSData *data;\n    if ([typeStr isEqualToString:@\"url\"]) {\n        data =[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:filePath]];\n    }else if([typeStr isEqualToString:@\"file\"]){\n        data= [NSData dataWithContentsOfFile:filePath];\n        \n    }\n    \n    [DPAudioPlayer.sharedInstance startPlayWithData:data];\n    DPAudioPlayer.sharedInstance.playComplete = ^void(){\n        NSLog(@\"ios-----播放完成----by playbyPath---\");\n        NSDictionary *args =   [self->_call arguments];\n        NSString *mId = [args valueForKey:@\"id\"];\n        NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:filePath,@\"playPath\",@\"complete\",@\"playState\",mId,@\"id\", nil];\n        [self->_channel invokeMethod:@\"onPlayState\" arguments:dict3];\n    };\n    \n    NSString *mId = [args valueForKey:@\"id\"];\n    NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@\"success\",@\"result\",mId,@\"id\", nil];\n    [_channel invokeMethod:@\"onPlay\" arguments:dict3];\n}\n\n\n@end\n\n"
  },
  {
    "path": "ios/Classes/JX_GCDTimerManager.h",
    "content": "\n#import <Foundation/Foundation.h>\n\ntypedef enum : NSUInteger {\n    AbandonPreviousAction, // 废除之前的任务\n    MergePreviousAction    // 将之前的任务合并到新的任务中\n} ActionOption;\n\n@interface JX_GCDTimerManager : NSObject\n\n+ (JX_GCDTimerManager *)sharedInstance;\n\n/**\n 启动一个timer，默认精度为0.1秒\n \n @param timerName       timer的名称，作为唯一标识\n @param interval        执行的时间间隔\n @param queue           timer将被放入的队列，也就是最终action执行的队列。传入nil将自动放到一个子线程队列中\n @param repeats         timer是否循环调用\n @param option          多次schedule同一个timer时的操作选项(目前提供将之前的任务废除或合并的选项)\n @param action          时间间隔到点时执行的block\n */\n- (void)scheduledDispatchTimerWithName:(NSString *)timerName\n                          timeInterval:(double)interval\n                                 queue:(dispatch_queue_t)queue\n                               repeats:(BOOL)repeats\n                          actionOption:(ActionOption)option\n                                action:(dispatch_block_t)action;\n\n/**\n 撤销某个timer\n \n @param timerName timer的名称，作为唯一标识\n */\n- (void)cancelTimerWithName:(NSString *)timerName;\n\n/**\n 撤销所有的timer\n */\n- (void)cancelAllTimer;\n\n@end\n"
  },
  {
    "path": "ios/Classes/JX_GCDTimerManager.m",
    "content": "\n#import \"JX_GCDTimerManager.h\"\n\n@interface JX_GCDTimerManager()\n\n@property (nonatomic, strong) NSMutableDictionary *timerContainer;\n@property (nonatomic, strong) NSMutableDictionary *actionBlockCache;\n\n@end\n\n@implementation JX_GCDTimerManager\n\n#pragma mark - Public Method\n\n+ (JX_GCDTimerManager *)sharedInstance\n{\n    static JX_GCDTimerManager *_gcdTimerManager = nil;\n    static dispatch_once_t onceToken;\n    \n    dispatch_once(&onceToken,^{\n        _gcdTimerManager = [[JX_GCDTimerManager alloc] init];\n    });\n    \n    return _gcdTimerManager;\n}\n\n- (void)scheduledDispatchTimerWithName:(NSString *)timerName\n                          timeInterval:(double)interval\n                                 queue:(dispatch_queue_t)queue\n                               repeats:(BOOL)repeats\n                          actionOption:(ActionOption)option\n                                action:(dispatch_block_t)action\n{\n    if (nil == timerName)\n        return;\n    \n    if (nil == queue)\n        queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);\n    \n    dispatch_source_t timer = [self.timerContainer objectForKey:timerName];\n    if (!timer) {\n        timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);\n        dispatch_resume(timer);\n        [self.timerContainer setObject:timer forKey:timerName];\n    }\n    \n    /* timer精度为0.1秒 */\n    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);\n    \n    __weak typeof(self) weakSelf = self;\n    \n    switch (option) {\n            \n        case AbandonPreviousAction:\n        {\n            /* 移除之前的action */\n            [weakSelf removeActionCacheForTimer:timerName];\n            \n            dispatch_source_set_event_handler(timer, ^{\n                action();\n                \n                if (!repeats) {\n                    [weakSelf cancelTimerWithName:timerName];\n                }\n            });\n        }\n            break;\n            \n        case MergePreviousAction:\n        {\n            /* cache本次的action */\n            [self cacheAction:action forTimer:timerName];\n            \n            dispatch_source_set_event_handler(timer, ^{\n                NSMutableArray *actionArray = [self.actionBlockCache objectForKey:timerName];\n                [actionArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {\n                    dispatch_block_t actionBlock = obj;\n                    actionBlock();\n                }];\n                [weakSelf removeActionCacheForTimer:timerName];\n                \n                if (!repeats) {\n                    [weakSelf cancelTimerWithName:timerName];\n                }\n            });\n        }\n            break;\n    }\n}\n\n- (void)cancelTimerWithName:(NSString *)timerName\n{\n    dispatch_source_t timer = [self.timerContainer objectForKey:timerName];\n    \n    if (!timer) {\n        return;\n    }\n    \n    [self.timerContainer removeObjectForKey:timerName];\n    dispatch_source_cancel(timer);\n    \n    [self.actionBlockCache removeObjectForKey:timerName];\n}\n\n- (void)cancelAllTimer\n{\n    // Fast Enumeration\n    [self.timerContainer enumerateKeysAndObjectsUsingBlock:^(NSString *timerName, dispatch_source_t timer, BOOL *stop) {\n        [self.timerContainer removeObjectForKey:timerName];\n        dispatch_source_cancel(timer);\n    }];\n}\n\n#pragma mark - Property\n\n- (NSMutableDictionary *)timerContainer\n{\n    if (!_timerContainer) {\n        _timerContainer = [[NSMutableDictionary alloc] init];\n    }\n    return _timerContainer;\n}\n\n- (NSMutableDictionary *)actionBlockCache\n{\n    if (!_actionBlockCache) {\n        _actionBlockCache = [[NSMutableDictionary alloc] init];\n    }\n    return _actionBlockCache;\n}\n\n#pragma mark - Private Method\n\n- (void)cacheAction:(dispatch_block_t)action forTimer:(NSString *)timerName\n{\n    id actionArray = [self.actionBlockCache objectForKey:timerName];\n    \n    if (actionArray && [actionArray isKindOfClass:[NSMutableArray class]]) {\n        [(NSMutableArray *)actionArray addObject:action];\n    }else {\n        NSMutableArray *array = [NSMutableArray arrayWithObject:action];\n        [self.actionBlockCache setObject:array forKey:timerName];\n    }\n}\n\n- (void)removeActionCacheForTimer:(NSString *)timerName\n{\n    if (![self.actionBlockCache objectForKey:timerName])\n        return;\n    \n    [self.actionBlockCache removeObjectForKey:timerName];\n}\n\n@end\n"
  },
  {
    "path": "ios/flutter_plugin_record.podspec",
    "content": "#\n# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html\n#\n#use_frameworks!\nPod::Spec.new do |s|\n  s.name             = 'flutter_plugin_record'\n  s.version          = '0.0.1'\n  s.summary          = 'A new Flutter plugin.'\n  s.description      = <<-DESC\nA new Flutter plugin.\n                       DESC\n  s.homepage         = 'http://example.com'\n  s.license          = { :file => '../LICENSE' }\n  s.author           = { 'Your Company' => 'email@example.com' }\n  s.source           = { :path => '.' }\n#  s.swift_version = '4.2'\n  s.source_files = 'Classes/**/*.{h,m}'\n  s.public_header_files = 'Classes/**/*.h'\n#  s.vendored_libraries = 'Classes/libopencore-amrnb.a'\n  s.dependency 'Flutter'\n  s.framework  = \"AVFoundation\"\n  s.ios.deployment_target = '8.0'\nend\n\n"
  },
  {
    "path": "lib/const/play_state.dart",
    "content": "class PlayState {\n  String playState;\n  String playPath;\n\n  PlayState(this.playState, this.playPath);\n\n}\n"
  },
  {
    "path": "lib/const/record_state.dart",
    "content": "class RecordState {\n  ///发送到原生端的方法名\n  static String init = \"init\";\n  static String start = \"start\";\n  static String startByWavPath = \"startByWavPath\";\n  static String stop = \"stop\";\n  static String play = \"play\";\n  static String playByPath = \"playByPath\";\n\n  ///原生端的回调方法名\n  static String onInit = \"onInit\";\n  static String onStart = \"onStart\";\n  static String onStop = \"onStop\";\n  static String onPlay = \"onPlay\";\n  static String onAmplitude = \"onAmplitude\";\n  static String onPlayState = \"onPlayState\";\n}\n"
  },
  {
    "path": "lib/const/response.dart",
    "content": "class RecordResponse {\n  bool? success;\n  String? path;\n  String? msg;\n  String? key;\n  double? audioTimeLength;\n\n  RecordResponse(\n      {this.success, this.path, this.msg, this.key, this.audioTimeLength});\n//  RecordResponse({this.success, this.path,this.msg,this.key});\n\n}\n"
  },
  {
    "path": "lib/flutter_plugin_record.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter/services.dart';\nimport 'package:flutter_plugin_record/const/play_state.dart';\nimport 'package:flutter_plugin_record/const/response.dart';\nimport 'package:uuid/uuid.dart';\n\nclass FlutterPluginRecord {\n  final MethodChannel _channel = const MethodChannel('flutter_plugin_record');\n\n  static final _uuid = new Uuid();\n  String id = '';\n  static final alis = new Map<String, FlutterPluginRecord>();\n\n  FlutterPluginRecord() {\n    id = _uuid.v4();\n    alis[id] = this;\n    // print(\"--------FlutterPluginRecord init\");\n  }\n\n  ///Flutter  调用原生初始化\n  Future<dynamic> _invokeMethod(String method,\n      [Map<String, dynamic> arguments = const {}]) {\n    Map<String, dynamic> withId = Map.of(arguments);\n    withId['id'] = id;\n    _channel.setMethodCallHandler(_handler);\n    return _channel.invokeMethod(method, withId);\n  }\n\n  ///初始化init的回调\n  StreamController<bool> _responseInitController =\n      new StreamController.broadcast();\n\n  Stream<bool> get responseFromInit => _responseInitController.stream;\n\n  ///开始录制 停止录制的回调监听\n  StreamController<RecordResponse> _responseController =\n      new StreamController.broadcast();\n\n  Stream<RecordResponse> get response => _responseController.stream;\n\n  ///音量高低的回调\n  StreamController<RecordResponse> _responseAmplitudeController =\n      new StreamController.broadcast();\n\n  Stream<RecordResponse> get responseFromAmplitude =>\n      _responseAmplitudeController.stream;\n\n  ///播放状态监听\n  StreamController<PlayState> _responsePlayStateController =\n      new StreamController.broadcast();\n\n  Stream<PlayState> get responsePlayStateController =>\n      _responsePlayStateController.stream;\n\n  ///原生回调\n  static Future<dynamic> _handler(MethodCall methodCall) async{\n    // print(\"--------FlutterPluginRecord \" + methodCall.method);\n\n    String id = (methodCall.arguments as Map)['id'];\n    FlutterPluginRecord recordPlugin = alis[id] ?? FlutterPluginRecord();\n    switch (methodCall.method) {\n      case \"onInit\":\n        bool flag = false;\n        if (\"success\" == methodCall.arguments[\"result\"]) {\n          flag = true;\n        }\n        recordPlugin._responseInitController.add(flag);\n        break;\n      case \"onStart\":\n        if (\"success\" == methodCall.arguments[\"result\"]) {\n          RecordResponse res = new RecordResponse(\n            success: true,\n            path: \"\",\n            msg: \"onStart\",\n            key: methodCall.arguments[\"key\"].toString(),\n          );\n          recordPlugin._responseController.add(res);\n        }\n\n        break;\n      case \"onStop\":\n        if (\"success\" == methodCall.arguments[\"result\"]) {\n          RecordResponse res = new RecordResponse(\n            success: true,\n            path: methodCall.arguments[\"voicePath\"].toString(),\n            audioTimeLength:\n                double.parse(methodCall.arguments[\"audioTimeLength\"]),\n            msg: \"onStop\",\n            key: methodCall.arguments[\"key\"].toString(),\n          );\n          recordPlugin._responseController.add(res);\n        }\n\n        break;\n      case \"onPlay\":\n        RecordResponse res = new RecordResponse(\n          success: true,\n          path: \"\",\n          msg: \"开始播放\",\n          key: methodCall.arguments[\"key\"].toString(),\n        );\n        recordPlugin._responseController.add(res);\n        break;\n      case \"onAmplitude\":\n        if (\"success\" == methodCall.arguments[\"result\"]) {\n          RecordResponse res = new RecordResponse(\n            success: true,\n            path: \"\",\n            msg: methodCall.arguments[\"amplitude\"].toString(),\n            key: methodCall.arguments[\"key\"].toString(),\n          );\n          recordPlugin._responseAmplitudeController.add(res);\n        }\n        break;\n      case \"onPlayState\":\n        var playState = methodCall.arguments[\"playState\"];\n        var playPath = methodCall.arguments[\"playPath\"];\n        PlayState res = new PlayState(playState, playPath);\n        recordPlugin._responsePlayStateController.add(res);\n        break;\n      case \"pausePlay\":\n        //暂停或继续播放\n        var isPlaying = methodCall.arguments[\"isPlaying\"];\n        PlayState res = new PlayState(isPlaying, \"\");\n        recordPlugin._responsePlayStateController.add(res);\n        break;\n      default:\n        print(\"default\");\n        break;\n    }\n    return null;\n  }\n\n  //初始化\n  Future init() async {\n    return await _invokeMethod('init', <String, String>{\n      \"init\": \"init\",\n    });\n  }\n\n  //初始化\n  Future initRecordMp3() async {\n    return await _invokeMethod('initRecordMp3', <String, String>{\n      \"initRecordMp3\": \"initRecordMp3\",\n    });\n  }\n\n  Future start() async {\n    return await _invokeMethod('start', <String, String>{\n      \"start\": \"start\",\n    });\n  }\n\n  Future startByWavPath(String wavPath) async {\n    return await _invokeMethod('startByWavPath', <String, String>{\n      \"wavPath\": wavPath,\n    });\n  }\n\n  Future stop() async {\n    return await _invokeMethod('stop', <String, String>{\n      \"stop\": \"stop\",\n    });\n  }\n\n  Future play() async {\n    return await _invokeMethod('play', <String, String>{\n      \"play\": \"play\",\n    });\n  }\n\n//  Future playByPath(String path) async {\n//    return await _invokeMethod('playByPath', <String, String>{\n//      \"play\": \"play\",\n//      \"path\": path,\n//    });\n//  }\n\n  ///\n  /// 参数 path  播放音频的地址\n  ///\n  ///path 为 url类型对应的在线播放地址   https://linjuli-app-audio.oss-cn-hangzhou.aliyuncs.com/audio/50c39c768b534ce1ba25d837ed153824.wav\n  ///path 对应本地文件路径对应的是本地文件播放肚子   /sdcard/flutterdemo/wiw.wav\n  /// 参数  type\n  /// 当path 为url   type为 url\n  /// 当path 为本地地址 type为 file\n  ///\n  Future playByPath(String path, String type) async {\n    return await _invokeMethod('playByPath', <String, String>{\n      \"play\": \"play\",\n      \"path\": path,\n      \"type\": type,\n    });\n  }\n\n  ///暂停播放\n  Future pausePlay() async {\n    return await _invokeMethod('pause', <String, String>{\n      \"pause\": \"pause\",\n    });\n  }\n\n  /// 提供停止播放的功能\n  Future stopPlay() async {\n    return await _invokeMethod('stopPlay', <String, String>{});\n  }\n\n  dispose() {\n//     stopPlay();\n    _responseInitController.close();\n    _responseController.close();\n    _responseAmplitudeController.close();\n    _responsePlayStateController.close();\n  }\n}\n"
  },
  {
    "path": "lib/index.dart",
    "content": "export 'const/record_state.dart';\nexport 'flutter_plugin_record.dart';\nexport 'utils/common_toast.dart';\nexport 'widgets/custom_overlay.dart';\nexport 'widgets/voice_widget.dart';\n"
  },
  {
    "path": "lib/utils/common_toast.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/widgets/custom_overlay.dart';\n\nclass CommonToast {\n  static showView({\n    BuildContext? context,\n    String? msg,\n    TextStyle? style,\n    Widget? icon,\n    Duration duration = const Duration(seconds: 1),\n    int count = 3,\n    Function? onTap,\n  }) {\n    OverlayEntry? overlayEntry;\n    int _count = 0;\n\n    void removeOverlay() {\n      overlayEntry?.remove();\n      overlayEntry = null;\n    }\n\n    if (overlayEntry == null) {\n      overlayEntry = new OverlayEntry(builder: (content) {\n        return Container(\n          child: GestureDetector(\n            onTap: () {\n              if (onTap != null) {\n                removeOverlay();\n                onTap();\n              }\n            },\n            child: CustomOverlay(\n              icon: Column(\n                children: [\n                  Padding(\n                    child: icon,\n                    padding: const EdgeInsets.only(\n                      bottom: 10.0,\n                    ),\n                  ),\n                  Container(\n//                      padding: EdgeInsets.only(right: 20, left: 20, top: 0),\n                    child: Text(\n                      msg ?? '',\n                      style: style ??\n                          TextStyle(\n                            fontStyle: FontStyle.normal,\n                            color: Colors.white,\n                            fontSize: 16,\n                          ),\n                    ),\n                  )\n                ],\n              ),\n            ),\n          ),\n        );\n      });\n      Overlay.of(context!)!.insert(overlayEntry!);\n      if (onTap != null) return;\n      Timer.periodic(duration, (timer) {\n        _count++;\n        if (_count == count) {\n          _count = 0;\n          timer.cancel();\n          removeOverlay();\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "lib/widgets/custom_overlay.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass CustomOverlay extends StatelessWidget {\n  final Widget? icon;\n  final BoxDecoration decoration;\n  final double width;\n  final double height;\n  const CustomOverlay({\n    Key? key,\n    this.icon,\n    this.decoration = const BoxDecoration(\n      color: Color(0xff77797A),\n      borderRadius: BorderRadius.all(Radius.circular(20.0)),\n    ),\n    this.width = 160,\n    this.height = 160,\n  }) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Positioned(\n      top: MediaQuery.of(context).size.height * 0.5 - width / 2,\n      left: MediaQuery.of(context).size.width * 0.5 - height / 2,\n      child: Material(\n        type: MaterialType.transparency,\n        child: Center(\n          child: Opacity(\n            opacity: 0.8,\n            child: Container(\n              width: width,\n              height: height,\n              decoration: decoration,\n              child: icon,\n            ),\n          ),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "lib/widgets/voice_widget.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_plugin_record/flutter_plugin_record.dart';\nimport 'package:flutter_plugin_record/utils/common_toast.dart';\n\nimport 'custom_overlay.dart';\n\ntypedef startRecord = Future Function();\ntypedef stopRecord = Future Function();\n\nclass VoiceWidget extends StatefulWidget {\n  final Function? startRecord;\n  final Function? stopRecord;\n  final double? height;\n  final EdgeInsets? margin;\n  final Decoration? decoration;\n\n  /// startRecord 开始录制回调  stopRecord回调\n  const VoiceWidget(\n      {Key? key,\n      this.startRecord,\n      this.stopRecord,\n      this.height,\n      this.decoration,\n      this.margin})\n      : super(key: key);\n\n  @override\n  _VoiceWidgetState createState() => _VoiceWidgetState();\n}\n\nclass _VoiceWidgetState extends State<VoiceWidget> {\n  // 倒计时总时长\n  int _countTotal = 12;\n  double starty = 0.0;\n  double offset = 0.0;\n  bool isUp = false;\n  String textShow = \"按住说话\";\n  String toastShow = \"手指上滑,取消发送\";\n  String voiceIco = \"images/voice_volume_1.png\";\n\n  ///默认隐藏状态\n  bool voiceState = true;\n  FlutterPluginRecord? recordPlugin;\n  Timer? _timer;\n  int _count = 0;\n  OverlayEntry? overlayEntry;\n\n  @override\n  void initState() {\n    super.initState();\n    recordPlugin = new FlutterPluginRecord();\n\n    _init();\n\n    ///初始化方法的监听\n    recordPlugin?.responseFromInit.listen((data) {\n      if (data) {\n        print(\"初始化成功\");\n      } else {\n        print(\"初始化失败\");\n      }\n    });\n\n    /// 开始录制或结束录制的监听\n    recordPlugin?.response.listen((data) {\n      if (data.msg == \"onStop\") {\n        ///结束录制时会返回录制文件的地址方便上传服务器\n        print(\"onStop  \" + data.path!);\n        if (widget.stopRecord != null)\n          widget.stopRecord!(data.path, data.audioTimeLength);\n      } else if (data.msg == \"onStart\") {\n        print(\"onStart --\");\n        if (widget.startRecord != null) widget.startRecord!();\n      }\n    });\n\n    ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式\n    recordPlugin!.responseFromAmplitude.listen((data) {\n      var voiceData = double.parse(data.msg ?? '');\n      setState(() {\n        if (voiceData > 0 && voiceData < 0.1) {\n          voiceIco = \"images/voice_volume_2.png\";\n        } else if (voiceData > 0.2 && voiceData < 0.3) {\n          voiceIco = \"images/voice_volume_3.png\";\n        } else if (voiceData > 0.3 && voiceData < 0.4) {\n          voiceIco = \"images/voice_volume_4.png\";\n        } else if (voiceData > 0.4 && voiceData < 0.5) {\n          voiceIco = \"images/voice_volume_5.png\";\n        } else if (voiceData > 0.5 && voiceData < 0.6) {\n          voiceIco = \"images/voice_volume_6.png\";\n        } else if (voiceData > 0.6 && voiceData < 0.7) {\n          voiceIco = \"images/voice_volume_7.png\";\n        } else if (voiceData > 0.7 && voiceData < 1) {\n          voiceIco = \"images/voice_volume_7.png\";\n        } else {\n          voiceIco = \"images/voice_volume_1.png\";\n        }\n        if (overlayEntry != null) {\n          overlayEntry!.markNeedsBuild();\n        }\n      });\n\n      print(\"振幅大小   \" + voiceData.toString() + \"  \" + voiceIco);\n    });\n  }\n\n  ///显示录音悬浮布局\n  buildOverLayView(BuildContext context) {\n    if (overlayEntry == null) {\n      overlayEntry = new OverlayEntry(builder: (content) {\n        return CustomOverlay(\n          icon: Column(\n            children: <Widget>[\n              Container(\n                margin: const EdgeInsets.only(top: 10),\n                child: _countTotal - _count < 11\n                    ? Center(\n                        child: Padding(\n                          padding: const EdgeInsets.only(bottom: 15.0),\n                          child: Text(\n                            (_countTotal - _count).toString(),\n                            style: TextStyle(\n                              fontSize: 70.0,\n                              color: Colors.white,\n                            ),\n                          ),\n                        ),\n                      )\n                    : new Image.asset(\n                        voiceIco,\n                        width: 100,\n                        height: 100,\n                        package: 'flutter_plugin_record',\n                      ),\n              ),\n              Container(\n//                      padding: const EdgeInsets.only(right: 20, left: 20, top: 0),\n                child: Text(\n                  toastShow,\n                  style: TextStyle(\n                    fontStyle: FontStyle.normal,\n                    color: Colors.white,\n                    fontSize: 14,\n                  ),\n                ),\n              )\n            ],\n          ),\n        );\n      });\n      Overlay.of(context)!.insert(overlayEntry!);\n    }\n  }\n\n  showVoiceView() {\n    setState(() {\n      textShow = \"松开结束\";\n      voiceState = false;\n    });\n\n    ///显示录音悬浮布局\n    buildOverLayView(context);\n\n    start();\n  }\n\n  hideVoiceView() {\n    if (_timer!.isActive) {\n      if (_count < 1) {\n        CommonToast.showView(\n            context: context,\n            msg: '说话时间太短',\n            icon: Text(\n              '!',\n              style: TextStyle(fontSize: 80, color: Colors.white),\n            ));\n        isUp = true;\n      }\n      _timer?.cancel();\n      _count = 0;\n    }\n\n    setState(() {\n      textShow = \"按住说话\";\n      voiceState = true;\n    });\n\n    stop();\n    if (overlayEntry != null) {\n      overlayEntry?.remove();\n      overlayEntry = null;\n    }\n\n    if (isUp) {\n      print(\"取消发送\");\n    } else {\n      print(\"进行发送\");\n    }\n  }\n\n  moveVoiceView() {\n    // print(offset - start);\n    setState(() {\n      isUp = starty - offset > 100 ? true : false;\n      if (isUp) {\n        textShow = \"松开手指,取消发送\";\n        toastShow = textShow;\n      } else {\n        textShow = \"松开结束\";\n        toastShow = \"手指上滑,取消发送\";\n      }\n    });\n  }\n\n  ///初始化语音录制的方法\n  void _init() async {\n    recordPlugin?.init();\n  }\n\n  ///开始语音录制的方法\n  void start() async {\n    recordPlugin?.start();\n  }\n\n  ///停止语音录制的方法\n  void stop() {\n    recordPlugin?.stop();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      child: GestureDetector(\n        onLongPressStart: (details) {\n          starty = details.globalPosition.dy;\n          _timer = Timer.periodic(Duration(milliseconds: 1000), (t) {\n            _count++;\n            print('_count is 👉 $_count');\n            if (_count == _countTotal) {\n              hideVoiceView();\n            }\n          });\n          showVoiceView();\n        },\n        onLongPressEnd: (details) {\n          hideVoiceView();\n        },\n        onLongPressMoveUpdate: (details) {\n          offset = details.globalPosition.dy;\n          moveVoiceView();\n        },\n        child: Container(\n          height: widget.height ?? 60,\n          // color: Colors.blue,\n          decoration: widget.decoration ??\n              BoxDecoration(\n                borderRadius: new BorderRadius.circular(6.0),\n                border: Border.all(width: 1.0, color: Colors.grey.shade200),\n              ),\n          margin: widget.margin ?? EdgeInsets.fromLTRB(50, 0, 50, 20),\n          child: Center(\n            child: Text(\n              textShow,\n            ),\n          ),\n        ),\n      ),\n    );\n  }\n\n  @override\n  void dispose() {\n    recordPlugin?.dispose();\n    _timer?.cancel();\n    super.dispose();\n  }\n}\n"
  },
  {
    "path": "pubspec.yaml",
    "content": "\nname: flutter_plugin_record\ndescription: The flutter voice recording plug-in,provides the recording animation and the recording successfully returns to the recording file path\nversion: 1.0.1\n#author:  wilson <yxw_android@163.com>\nhomepage: https://github.com/yxwandroid/flutter_plugin_record\n#publish_to: none\n\n\nenvironment:\n  sdk: \">=2.12.0-29.10.beta <3.0.0\"\n  flutter: \">=1.12.0\"\n\n\ndependencies:\n  flutter:\n    sdk: flutter\n  uuid: ^3.0.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n\n# For information on the generic Dart part of this file, see the\n# following page: https://dart.dev/tools/pub/pubspec\n\n# The following section is specific to Flutter.\nflutter:\n  # This section identifies this Flutter project as a plugin project.\n  # The androidPackage and pluginClass identifiers should not ordinarily\n  # be modified. They are used by the tooling to maintain consistency when\n  # adding or updating assets for this project.\n  plugin:\n#    androidPackage: record.wilson.flutter.com.flutter_plugin_record\n#    pluginClass: FlutterPluginRecordPlugin\n    platforms:\n      android:\n        package: record.wilson.flutter.com.flutter_plugin_record\n        pluginClass: FlutterPluginRecordPlugin\n      ios:\n        pluginClass: FlutterPluginRecordPlugin\n\n  uses-material-design: true\n  assets:\n    - images/voice_volume_1.png\n    - images/voice_volume_2.png\n    - images/voice_volume_3.png\n    - images/voice_volume_4.png\n    - images/voice_volume_5.png\n    - images/voice_volume_6.png\n    - images/voice_volume_7.png\n\n  # To add assets to your plugin package, add an assets section, like this:\n  # assets:\n  #  - images/a_dot_burr.jpeg\n  #  - images/a_dot_ham.jpeg\n  #\n  # For details regarding assets in packages, see\n  # https://flutter.dev/assets-and-images/#from-packages\n  #\n  # An image asset can refer to one or more resolution-specific \"variants\", see\n  # https://flutter.dev/assets-and-images/#resolution-aware.\n\n  # To add custom fonts to your plugin package, add a fonts section here,\n  # in this \"flutter\" section. Each entry in this list should have a\n  # \"family\" key with the font family name, and a \"fonts\" key with a\n  # list giving the asset and other descriptors for the font. For\n  # example:\n  # fonts:\n  #   - family: Schyler\n  #     fonts:\n  #       - asset: fonts/Schyler-Regular.ttf\n  #       - asset: fonts/Schyler-Italic.ttf\n  #         style: italic\n  #   - family: Trajan Pro\n  #     fonts:\n  #       - asset: fonts/TrajanPro.ttf\n  #       - asset: fonts/TrajanPro_Bold.ttf\n  #         weight: 700\n  #\n  # For details regarding fonts in packages, see\n  # https://flutter.dev/custom-fonts/#from-packages\n"
  },
  {
    "path": "test/flutter_plugin_record_test.dart",
    "content": "import 'package:flutter/services.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const MethodChannel channel = MethodChannel('flutter_plugin_record');\n\n  setUp(() {\n    channel.setMockMethodCallHandler((MethodCall methodCall) async {\n      return '42';\n    });\n  });\n\n  tearDown(() {\n    channel.setMockMethodCallHandler(null);\n  });\n\n//  test('getPlatformVersion', () async {\n//    expect(await FlutterPluginRecord.platformVersion, '42');\n//  });\n}\n"
  }
]