├── .gitattributes
├── .gitignore
├── .idea
├── libraries
│ └── Flutter_Plugins.xml
├── modules.xml
├── runConfigurations
│ └── example_lib_main_dart.xml
└── vcs.xml
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README_images
├── f53502b3.png
├── ios.gif
├── ios_error.png
└── video2gif_20191118_101627.gif
├── android
├── .gitignore
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── record
│ └── wilson
│ └── flutter
│ └── com
│ └── flutter_plugin_record
│ ├── FlutterPluginRecordPlugin.kt
│ ├── timer
│ ├── ITimer.java
│ ├── ITimerChangeCallback.java
│ ├── MTimer.java
│ └── TimerUtils.java
│ └── utils
│ ├── AudioHandler.java
│ ├── DateUtils.java
│ ├── DialogUtil.java
│ ├── FileTool.java
│ ├── LogUtils.java
│ ├── PlayState.java
│ ├── PlayUtilsPlus.java
│ └── RecorderUtil.java
├── example
├── .flutter-plugins-dependencies
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── record
│ │ │ │ │ └── wilson
│ │ │ │ │ └── flutter
│ │ │ │ │ └── com
│ │ │ │ │ └── flutter_plugin_record_example
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values
│ │ │ │ └── styles.xml
│ │ │ │ └── xml
│ │ │ │ └── network_security_config.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── res
│ │ └── values
│ │ │ └── strings_en.arb
│ └── settings.gradle
├── ios
│ ├── Flutter
│ │ ├── .last_build_id
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ ├── Flutter.podspec
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
├── lib
│ ├── generated
│ │ └── i18n.dart
│ ├── main.dart
│ ├── path_provider_screen.dart
│ ├── record_mp3_screen.dart
│ ├── record_screen.dart
│ └── wechat_record_screen.dart
├── pubspec.lock
├── pubspec.yaml
└── test
│ └── widget_test.dart
├── flutter_plugin_record.iml
├── images
├── voice_volume_1.png
├── voice_volume_2.png
├── voice_volume_3.png
├── voice_volume_4.png
├── voice_volume_5.png
├── voice_volume_6.png
└── voice_volume_7.png
├── ios
├── .gitignore
├── Assets
│ └── .gitkeep
├── Classes
│ ├── DPAudioPlayer.h
│ ├── DPAudioPlayer.m
│ ├── DPAudioRecorder.h
│ ├── DPAudioRecorder.m
│ ├── FlutterPluginRecordPlugin.h
│ ├── FlutterPluginRecordPlugin.m
│ ├── JX_GCDTimerManager.h
│ └── JX_GCDTimerManager.m
└── flutter_plugin_record.podspec
├── lib
├── const
│ ├── play_state.dart
│ ├── record_state.dart
│ └── response.dart
├── flutter_plugin_record.dart
├── index.dart
├── utils
│ └── common_toast.dart
└── widgets
│ ├── custom_overlay.dart
│ └── voice_widget.dart
├── pubspec.lock
├── pubspec.yaml
├── test
└── flutter_plugin_record_test.dart
└── 飞云之下.wav
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.yml linguist-language=Dart
2 | *.java linguist-language=Dart
3 | *.m linguist-language=Dart
4 | *.h linguist-language=Dart
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 | .idea/
--------------------------------------------------------------------------------
/.idea/libraries/Flutter_Plugins.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/example_lib_main_dart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 68587a0916366e9512a78df22c44163d041dd5f3
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.1
2 |
3 | * 录音时长类型修改为 Double(和 iOS 端保持一致)
4 |
5 | ## 1.0.0
6 |
7 | * 添加空安全相关逻辑
8 | * 适配 flutter 2.0
9 |
10 | ## 0.3.5
11 |
12 | * 修复ios 音频监听灵敏性问题
13 |
14 | ## 0.3.4
15 |
16 | * 修复根据路径录制bug
17 |
18 | ## 0.3.3
19 |
20 | * 修复根据路径录制bug
21 |
22 | ## 0.3.2
23 |
24 | * 修复android 获取权限bug
25 |
26 | ## 0.3.1
27 |
28 | * 更新IOS podfile
29 |
30 | ## 0.3.0
31 | * 添加支持录制mp3
32 |
33 | ## 0.2.5
34 | * 修复权限申请bug
35 | ## 0.2.4
36 | * 修复播放音频文件的bug。
37 | ## 0.2.3
38 | * 修复bug
39 | ## 0.2.2
40 | * 修复bug
41 | ## 0.2.1
42 | * 补充说明文档
43 | ## 0.2.0
44 | * 实现暂停播放和继续播放功能
45 | ## 0.1.9
46 |
47 | * 注释掉初始化插件就申请权限的问题
48 |
49 | ## 0.1.8
50 |
51 | * 添加支持 Android 和IOS 播放在线wav音频
52 |
53 | ## 0.1.7
54 |
55 | * 修复bug在未使用录音功能前,通过playByPath播发音频,音频可以正常播放,但无法监听到播放结束
56 |
57 | ## 0.1.6
58 |
59 | * 添加android 在开始录制时进行权限验证判断
60 | ## 0.1.5
61 |
62 | * 实现根据传递的路径进行语音录制
63 |
64 | ## 0.1.4
65 |
66 | * 解决 android 申请权限失败问题
67 | * 解决 快速点击发送语音录制停止不了问题
68 |
69 | ## 0.1.3
70 |
71 | * 实现播放完成的回调监听
72 |
73 |
74 | ## 0.1.2
75 |
76 | * 实现播放指定路径录音文件
77 | ## 0.1.1
78 |
79 | * 格式代码
80 | ## 0.1.0
81 |
82 | * 修复提示bug
83 |
84 | ## 0.0.9
85 |
86 | * 添加记录录制时间的功能
87 |
88 | ## 0.0.8
89 |
90 | * 重构项目为oc项目 解决播放oc 工程无法使用问题
91 | ## 0.0.7
92 |
93 | * 更新readme
94 |
95 | ## 0.0.6
96 |
97 | * 适配android 9.0
98 | * 解决ios集成到oc项目不成功问题
99 |
100 | ## 0.0.5
101 |
102 | * 添加 readme 说明
103 |
104 | ## 0.0.4
105 |
106 | * 添加example readme
107 |
108 | ## 0.0.3
109 |
110 | * 添加引入方式
111 |
112 | ## 0.0.2
113 |
114 | * 添加license
115 |
116 | ## 0.0.1
117 |
118 | * 初始化项目
119 |
120 |
121 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [TOC]
2 |
3 |
4 | # 使用Flutter实现 仿微信录音的插件
5 | 插件支持android 和IOS
6 |
7 |
8 | -------
9 | 插件提供的功能
10 | 录制
11 | 1. 录制语音,
12 | 2. 播放录音,
13 | 3. 录制声音大小的监听
14 | 4. 提供录制时长的监听
15 | 5. 提供类似微信的录制组件
16 | 6. 提供播放音频结束的监听
17 | 7. 提供根据传递的路径进行语音录制
18 | 8. 提供录制wav,mp3格式 具体可参考example
19 |
20 | 播放
21 | 1. 提供播放指定路径的音频文件
22 | 2. 提供播放指定Url地址的wav,MP3格式文件
23 | 3. 提供播放完成的回调监听
24 | 4. 提供暂停和继续播放的功能
25 | 5. 提供停止播放的功能
26 |
27 |
28 |
29 |
30 | ## 1,引入
31 | 在pubspec.yaml 文件上引入如下配置
32 |
33 |
34 | 引入方式1(引入最新的版本)
35 | flutter_plugin_record:
36 | git:
37 | url: https://github.com/yxwandroid/flutter_plugin_record.git
38 |
39 | 引入方式2 (引入指定某次commit)
40 | flutter_plugin_record:
41 | git:
42 | url: https://github.com/yxwandroid/flutter_plugin_record.git
43 | ref: 29c02b15835907879451ad9f8f88c357149c6085
44 |
45 | 引入方式3 (引入Flutter仓库的library)
46 |
47 | dependencies:
48 | flutter_plugin_record: ^1.0.1
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | ### 使用
59 | ### 1, 初始化录制
60 | #### 1.1, 初始化录制(wav)
61 | 可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化
62 |
63 | //实例化对象
64 | FlutterPluginRecord recordPlugin = new FlutterPluginRecord();
65 | // 初始化
66 | recordPlugin.init()
67 |
68 |
69 | #### 1.2, 初始化录制(Mp3)
70 | 可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化
71 |
72 | //实例化对象
73 | FlutterPluginRecord recordPlugin = new FlutterPluginRecord();
74 | // 初始化
75 | recordPlugin.initRecordMp3()
76 |
77 | ### 2, 开始录制
78 |
79 | recordPlugin.start()
80 |
81 | ### 3, 停止录制
82 |
83 | recordPlugin.stop()
84 |
85 | ### 4, 播放
86 |
87 | #### 1,播放
88 |
89 | recordPlugin.play()
90 |
91 | #### 2, 暂停和继续播放
92 |
93 | recordPlugin.pausePlay();
94 |
95 | #### 3, 停止播放
96 |
97 | recordPlugin.stopPlay();
98 |
99 | ### 5, 根据传递的路径进行语音录制
100 |
101 | recordPlugin.startByWavPath(wavPath);
102 |
103 | ### 6, 根据传递的路径或则Url进行语音播放
104 |
105 |
106 | ///
107 | /// 参数 path 播放音频的地址
108 | ///
109 | ///path 为 url类型对应的在线播放地址 https://linjuli-app-audio.oss-cn-hangzhou.aliyuncs.com/audio/50c39c768b534ce1ba25d837ed153824.wav
110 | ///path 对应本地文件路径对应的是本地文件播放肚子 /sdcard/flutterdemo/wiw.wav
111 | /// 参数 type
112 | /// 当path 为url type为 url
113 | /// 当path 为本地地址 type为 file
114 | ///
115 | Future playByPath(String path, String type) async {
116 | return await _invokeMethod('playByPath', {
117 | "play": "play",
118 | "path": path,
119 | "type": type,
120 | });
121 | }
122 |
123 | ### 7, 释放资源
124 | 可以在页面退出的时候进行资源释放 比如在 dispose方法中调用如下代码
125 |
126 | recordPlugin.dispose()
127 |
128 |
129 |
130 | ### 4,回调监听
131 | 1,初始化回调监听
132 |
133 |
134 | ///初始化方法的监听
135 | recordPlugin.responseFromInit.listen((data) {
136 | if (data) {
137 | print("初始化成功");
138 | } else {
139 | print("初始化失败");
140 | }
141 | });
142 |
143 |
144 | 2,开始录制停止录制监听
145 |
146 | /// 开始录制或结束录制的监听
147 | recordPlugin.response.listen((data) {
148 | if (data.msg == "onStop") {
149 | ///结束录制时会返回录制文件的地址方便上传服务器
150 | print("onStop " + data.path);
151 | } else if (data.msg == "onStart") {
152 | print("onStart --");
153 | }
154 | });
155 |
156 | 3,录制声音大小回调监听
157 |
158 |
159 | ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
160 | recordPlugin.responseFromAmplitude.listen((data) {
161 | var voiceData = double.parse(data.msg);
162 | var tempVoice = "";
163 | if (voiceData > 0 && voiceData < 0.1) {
164 | tempVoice = "images/voice_volume_2.png";
165 | } else if (voiceData > 0.2 && voiceData < 0.3) {
166 | tempVoice = "images/voice_volume_3.png";
167 | } else if (voiceData > 0.3 && voiceData < 0.4) {
168 | tempVoice = "images/voice_volume_4.png";
169 | } else if (voiceData > 0.4 && voiceData < 0.5) {
170 | tempVoice = "images/voice_volume_5.png";
171 | } else if (voiceData > 0.5 && voiceData < 0.6) {
172 | tempVoice = "images/voice_volume_6.png";
173 | } else if (voiceData > 0.6 && voiceData < 0.7) {
174 | tempVoice = "images/voice_volume_7.png";
175 | } else if (voiceData > 0.7 && voiceData < 1) {
176 | tempVoice = "images/voice_volume_7.png";
177 | }
178 | setState(() {
179 | voiceIco = tempVoice;
180 | if(overlayEntry!=null){
181 | overlayEntry.markNeedsBuild();
182 | }
183 | });
184 |
185 | print("振幅大小 " + voiceData.toString() + " " + voiceIco);
186 | });
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | 4,播放声音完成的监听监听
197 |
198 |
199 | recordPlugin.responsePlayStateController.listen((data){
200 | print("播放路径 " + data.playPath );
201 | print("播放状态 " + data.playState );
202 | });
203 |
204 |
205 |
206 | ## 2,录制组件的使用
207 |
208 | 组件使用效果
209 |
210 | android效果
211 |
212 |
213 |
214 |
215 | IOS效果
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | ### 1,在使用的页面进行导入package
225 |
226 | import 'package:flutter_plugin_record/index.dart';
227 |
228 |
229 |
230 |
231 |
232 | ### 2,在使用的地方引入VoiceWidget组件
233 |
234 | VoiceWidget(),
235 |
236 |
237 | VoiceWidget({startRecord: Function, stopRecord: Function}) {
238 |
239 |
240 |
241 | startRecord 开始录制的回调
242 |
243 | stopRecord 停止录制的回调 返回的path是录制成功之后文件的保存地址
244 |
245 |
246 |
247 | ## IOS配置注意事项
248 |
249 | ### ios集成的的时候需要在info.list添加
250 |
251 |
252 | NSMicrophoneUsageDescription
253 | 打开话筒
254 |
255 |
256 | NSAppTransportSecurity
257 |
258 | NSAllowsArbitraryLoads
259 |
260 |
261 |
262 |
263 |
264 | ### ios release 打包失败配置注意事项
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | ## android配置注意事项
273 |
274 | ### android 集成的的时候需要在application标签下添加
275 |
276 |
277 | tools:replace="android:label"
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 | ## TODO
287 |
288 | * [ ] 双声道切换 单声道切换
289 |
290 | ## 感谢
291 |
292 |
293 | [肖中旺](https://github.com/xzw421771880)对IOS 播放在线Wav的支持
294 |
295 |
296 | ## 作者的其他开源项目推荐
297 |
298 |
299 | [基于腾讯云点播封装的flutter版的播放器插件 ](https://github.com/yxwandroid/flutter_tencentplayer_plus)
300 |
301 | [Flutter 二维码扫描插件](https://github.com/yxwandroid/flutter_plugin_qrcode)
302 |
303 | [抖音开发平台SDK Flutter插件](https://github.com/yxwandroid/flutter_plugin_douyin_open)
304 |
305 | [FLutter地图插件](https://github.com/yxwandroid/flutter_amap_location)
306 |
307 | [Flutter 模板工程](https://github.com/yxwandroid/flutter_app_redux.git)
308 |
309 | ## 关注公众号获取更多内容
310 |
311 |
312 |
313 |
--------------------------------------------------------------------------------
/README_images/f53502b3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/README_images/f53502b3.png
--------------------------------------------------------------------------------
/README_images/ios.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/README_images/ios.gif
--------------------------------------------------------------------------------
/README_images/ios_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/README_images/ios_error.png
--------------------------------------------------------------------------------
/README_images/video2gif_20191118_101627.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/README_images/video2gif_20191118_101627.gif
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'record.wilson.flutter.com.flutter_plugin_record'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | // ext.kotlin_version = '1.2.71'
6 | ext.kotlin_version = '1.3.50'
7 |
8 | repositories {
9 | google()
10 | jcenter()
11 | }
12 |
13 | dependencies {
14 | classpath 'com.android.tools.build:gradle:3.2.1'
15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
16 | }
17 | }
18 |
19 | rootProject.allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven { url 'https://jitpack.io' }
24 | }
25 | }
26 |
27 | apply plugin: 'com.android.library'
28 | apply plugin: 'kotlin-android'
29 |
30 | android {
31 | compileSdkVersion 29
32 |
33 | sourceSets {
34 | main.java.srcDirs += 'src/main/kotlin'
35 | }
36 | defaultConfig {
37 | minSdkVersion 19
38 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
39 | }
40 | lintOptions {
41 | disable 'InvalidPackage'
42 | }
43 | }
44 |
45 | dependencies {
46 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
47 | implementation 'com.github.shaoshuai904:RecordWav:1.0.2'
48 | implementation "androidx.appcompat:appcompat:1.0.0"
49 | implementation 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'
50 | }
51 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
3 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'flutter_plugin_record'
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimer.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.timer;
2 |
3 |
4 |
5 | public interface ITimer {
6 | void startTimer();
7 |
8 | void pauseTimer();
9 |
10 | void resumeTimer();
11 |
12 | void stopTimer();
13 | }
14 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/ITimerChangeCallback.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.timer;
2 |
3 | public interface ITimerChangeCallback {
4 | void onTimeChange(long time);
5 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/MTimer.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.timer;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.util.Timer;
6 | import java.util.TimerTask;
7 | import java.util.concurrent.atomic.AtomicLong;
8 |
9 | public class MTimer implements ITimer {
10 | //--real timer--
11 | private Timer timer;
12 | //--default task--
13 | private TimerTask task;
14 | //--default initdelay--
15 | private long initDelay = 0l;
16 | //--default delay--
17 | private long delay = 0l;
18 | //--call back--
19 | private ITimerChangeCallback[] callbacks = null;
20 | //--real time--
21 | private AtomicLong time;//时间的记录工具
22 |
23 | //--action--
24 | private static final int START = 0;
25 | private static final int PAUSE = 1;
26 | private static final int RESUME = 2;
27 | private static final int STOP = 3;
28 |
29 | private int status = STOP;//默认是stop
30 |
31 | private MTimer(long initDelay, long delay, ITimerChangeCallback[] callbacks) {
32 | this.initDelay = initDelay;
33 | this.delay = delay;
34 | this.callbacks = callbacks;
35 | }
36 |
37 | //-----------------外部方法------------------------
38 |
39 | /**
40 | * 用于生成MTimer 对象
41 | *
42 | * @return --MTimer
43 | */
44 | public static Builder makeTimerBuilder() {
45 | return new Builder();
46 | }
47 |
48 | /**
49 | * 开启 timer
50 | */
51 | @Override
52 | public void startTimer() {
53 | //判断当前是不是stop,是的话开始运行
54 | if (status != STOP) {
55 | return;
56 | }
57 | //切换当前状态为 start
58 | status = START;
59 | realStartTimer(true);
60 | }
61 |
62 | /**
63 | * 暂停timer
64 | */
65 | @Override
66 | public void pauseTimer() {
67 | //判断当前是不是start 是不是resume,如果是其中一个就可以
68 | if (status != START && status != RESUME) {
69 | return;
70 | }
71 | //切换当前状态为 pause
72 | status = PAUSE;
73 | realStopTimer(false);
74 | }
75 |
76 | /**
77 | * 重启timer
78 | */
79 | @Override
80 | public void resumeTimer() {
81 | //判断当前是不是pause ,如果是则恢复
82 | if (status != PAUSE) {
83 | return;
84 | }
85 | //切换当前状态为 resume
86 | status = RESUME;
87 | realStartTimer(false);
88 | }
89 |
90 | /**
91 | * 关闭timer
92 | */
93 | @Override
94 | public void stopTimer() {
95 | //无论当前处于那种状态都可以stop
96 | status = STOP;
97 | realStopTimer(true);
98 | }
99 |
100 | //-----------------内部方法------------------------
101 |
102 | /**
103 | * timer 真正的开始方法
104 | *
105 | * @param isToZero --是否清除数据
106 | */
107 | private void realStartTimer(boolean isToZero) {
108 | //清空记录时间
109 | if (isToZero) {
110 | time = new AtomicLong(0);
111 | }
112 | //重新生成timer、task
113 | if (timer == null && task == null) {
114 | timer = new Timer();
115 | task = createTask();
116 | timer.scheduleAtFixedRate(task, initDelay, delay);
117 | }
118 | }
119 |
120 | /**
121 | * timer 真正的关闭方法
122 | *
123 | * @param isToZero --是否清除数据
124 | */
125 | private void realStopTimer(boolean isToZero) {
126 | //清空记录时间
127 | if (isToZero) {
128 | time = new AtomicLong(0);
129 | }
130 | //关闭当前的timer
131 | if (timer != null) {
132 | timer.purge();
133 | timer.cancel();
134 | timer = null;
135 |
136 | }
137 | //关闭当前任务
138 | if (task != null) {
139 | task.cancel();
140 | task = null;
141 | }
142 | }
143 |
144 |
145 | /**
146 | * 判断是否设置监听回调
147 | *
148 | * @return -- true 表示设置了回调,反之表示没设置
149 | */
150 | private boolean checkCallback() {
151 | return callbacks != null && callbacks.length > 0;
152 | }
153 |
154 | /**
155 | * 创建task
156 | *
157 | * @return
158 | */
159 | private TimerTask createTask() {
160 | TimerTask task = new TimerTask() {
161 | @Override
162 | public void run() {
163 | time.incrementAndGet();
164 | notifyCallback(time);
165 | }
166 | };
167 | return task;
168 | }
169 |
170 | /**
171 | * 通知callback
172 | *
173 | * @param time --间距走的次数(花费时间=次数*delay+initDelay)
174 | */
175 | private void notifyCallback(AtomicLong time) {
176 | if (checkCallback()) {
177 | for (ITimerChangeCallback callback : callbacks) {
178 | callback.onTimeChange(time.longValue());
179 | }
180 | }
181 | }
182 |
183 | public static class Builder {
184 |
185 | //--default initdelay--
186 | private long initDelay = 0l;
187 | //--default delay--
188 | private long delay = 0l;
189 | //--call back--
190 | private ITimerChangeCallback[] callbacks = null;
191 | //--tag--
192 | private String tag;
193 |
194 | public Builder setTag(String tag) {
195 | if (TextUtils.isEmpty(tag)) {
196 | throw new NullPointerException("设置的tag无效!=>setTag(String tag)");
197 | }
198 | this.tag = tag;
199 | return this;
200 | }
201 |
202 |
203 | /**
204 | * 设置执行当前任务的时候首次执行时的延迟时间
205 | *
206 | * @param initDelay --首次执行的延迟时间(ms)
207 | */
208 | public Builder setInitDelay(long initDelay) {
209 | this.initDelay = initDelay;
210 | return this;
211 | }
212 |
213 | /**
214 | * 设置时间回调
215 | *
216 | * @param callbacks
217 | */
218 | public Builder setCallbacks(ITimerChangeCallback... callbacks) {
219 | this.callbacks = callbacks;
220 | return this;
221 | }
222 |
223 | /**
224 | * 设置后续的延迟时间
225 | *
226 | * @param delay --后续延迟时间(ms)
227 | */
228 | public Builder setDelay(long delay) {
229 | this.delay = delay;
230 | return this;
231 | }
232 |
233 | /**
234 | * 外部会重用此对象,所以需要重置其参数
235 | */
236 | public void reset() {
237 | tag = null;
238 | initDelay = 0l;
239 | delay = 0l;
240 | callbacks = null;
241 | }
242 |
243 | /**
244 | * 最终的生成方法,如果不调用此处,timer无法运行
245 | */
246 | public MTimer build() {
247 | //--check delay--
248 | if (initDelay < 0 || delay < 0) {
249 | throw new AssertionError("initDelay或delay 不允许小于0");
250 | }
251 | //--build timer--
252 | MTimer timer = new MTimer(initDelay, delay, callbacks);
253 | //--add to cache--
254 | if (!TextUtils.isEmpty(tag)) {
255 | TimerUtils.addTimerToCache(tag, timer);
256 | }
257 | //--return timer--
258 | return timer;
259 | }
260 |
261 | }
262 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/timer/TimerUtils.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.timer;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import java.util.Locale;
7 | import java.util.WeakHashMap;
8 | /**
9 | * description: 定时器工具类
10 | * author: Simon
11 | * created at 2017/8/10 上午9:47
12 | */
13 |
14 | public final class TimerUtils {
15 | //--tag--
16 | private static final String TAG = "TimerUtils";
17 | //--err info--
18 | private static final String ERR_INFO = "未找到对应的MTimer,确认是否设置过Tag!=>new Builder().setTag(String tag)";
19 | //--cache--
20 | private static WeakHashMap cacheTimerMap = new WeakHashMap<>();
21 | //--action--
22 | private static final int START = 0;
23 | private static final int PAUSE = 1;
24 | private static final int RESUME = 2;
25 | private static final int STOP = 3;
26 |
27 | //--recycle build--
28 | private static final MTimer.Builder BUILDER = new MTimer.Builder();
29 |
30 | private TimerUtils() {
31 | throw new AssertionError("you can't init me!");
32 | }
33 |
34 | /**
35 | * 注意此方法,会重复利用Builder 对象,所以每次build()完成后再重新使用该方法!!
36 | *
37 | * @return --builder
38 | */
39 | public static MTimer.Builder makeBuilder() {
40 | BUILDER.reset();//每次执行的时候都会重置一次
41 | return BUILDER;
42 | }
43 |
44 | /**
45 | * 开启timer ,时间清零
46 | */
47 | public static void startTimer(String tag) {
48 | actionTimer(START, tag);
49 | }
50 |
51 | /**
52 | * 恢复timer,不清零
53 | */
54 | public static void resumeTimer(String tag) {
55 | actionTimer(RESUME, tag);
56 | }
57 |
58 | /**
59 | * 暂停timer
60 | */
61 | public static void pauseTimer(String tag) {
62 | actionTimer(PAUSE, tag);
63 | }
64 |
65 | /**
66 | * 关闭 timer
67 | */
68 | public static void stopTimer(String tag) {
69 | actionTimer(STOP, tag);
70 | }
71 |
72 | /**
73 | * 格式化 时间 格式为 hh:mm:ss
74 | *
75 | * @param cnt
76 | * @return
77 | */
78 | public static String formatTime(long cnt) {
79 | long hour = cnt / 3600;
80 | long min = cnt % 3600 / 60;
81 | long second = cnt % 60;
82 | return String.format(Locale.CHINA, "%02d:%02d:%02d", hour, min, second);
83 | }
84 |
85 | //------------------------私有方法/内部类------------------------------
86 |
87 | /**
88 | * 添加timer到缓存
89 | *
90 | * @param tag --tag
91 | * @param timer --timer
92 | */
93 | public static void addTimerToCache(String tag, MTimer timer) {
94 | if (cacheTimerMap == null) {
95 | cacheTimerMap = new WeakHashMap<>();
96 | }
97 | cacheTimerMap.put(tag, timer);
98 | }
99 |
100 | /**
101 | * 真正的执行方法
102 | *
103 | * @param action --行为
104 | * @param tag --tag
105 | */
106 | private static void actionTimer(int action, String tag) {
107 | //-----check tag----
108 | if (!checkTag(tag)) {
109 | Log.e(TAG, "The tag is empty or null!");
110 | return;
111 | }
112 | //-----check timer----
113 | MTimer timer = findMTimerByTag(tag);
114 | if (timer == null) {
115 | Log.e(TAG, "Can't found timer by tag!");
116 | return;
117 | }
118 |
119 | //-----action timer----
120 | switch (action) {
121 | case START:
122 | timer.startTimer();
123 | break;
124 | case RESUME:
125 | timer.resumeTimer();
126 | break;
127 | case PAUSE:
128 | timer.pauseTimer();
129 | break;
130 | case STOP:
131 | timer.stopTimer();
132 | break;
133 | }
134 |
135 |
136 | }
137 |
138 |
139 | /**
140 | * 通过tag获取mtimer
141 | *
142 | * @param tag --设置的tag
143 | * @return --MTimer
144 | */
145 | private static MTimer findMTimerByTag(String tag) {
146 | if (!checkTag(tag) || cacheTimerMap == null || cacheTimerMap.size() == 0) {//tag无效,没有缓存数据,返回null
147 | return null;
148 | } else {//反之根据tag返回
149 | return cacheTimerMap.get(tag);
150 | }
151 | }
152 |
153 | /**
154 | * 判断tag 是否有效
155 | *
156 | * @param tag --tag
157 | * @return true表示有效,反之无效
158 | */
159 | private static boolean checkTag(String tag) {
160 | return !TextUtils.isEmpty(tag);
161 | }
162 |
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DateUtils.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.utils;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Date;
5 |
6 | public class DateUtils {
7 | public static String dateToString(Date date) {
8 | String str = "yyyyMMddhhmmss";
9 | SimpleDateFormat format = new SimpleDateFormat(str);
10 | String dateFormat = format.format(date);
11 | return dateFormat;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/DialogUtil.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.utils;
2 |
3 | import android.app.Activity;
4 | import android.app.Dialog;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.app.AlertDialog;
11 | /**
12 | * 对话框管理
13 | * 打开app应用程序信息界面
14 | */
15 | public class DialogUtil {
16 |
17 | public static void Dialog(final Activity activity, String content) {
18 | Dialog deleteDialog = new AlertDialog.Builder(activity)
19 | .setTitle("提示")
20 | .setMessage("请进入应用信息界面开启录音权限")
21 | .setPositiveButton("确定",
22 | new DialogInterface.OnClickListener() {
23 | public void onClick(DialogInterface dialog, int which) {
24 | startSetting(activity);
25 | }
26 | })
27 | .create();
28 | deleteDialog.setCanceledOnTouchOutside(false);
29 | deleteDialog.setCancelable(false);
30 | deleteDialog.show();
31 | }
32 |
33 |
34 | /**
35 | * 启动app设置应用程序信息界面
36 | */
37 | public static void startSetting(Context context) {
38 | Intent intent = new Intent();
39 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
40 | // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
41 | if (Build.VERSION.SDK_INT >= 9) {
42 | intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
43 | intent.setData(Uri.fromParts("package", context.getPackageName(), null));
44 | } else if (Build.VERSION.SDK_INT <= 8) {
45 | intent.setAction(Intent.ACTION_VIEW);
46 | intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
47 | intent.putExtra(context.getPackageName(), context.getPackageName());
48 | }
49 | context.startActivity(intent);
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayState.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.utils;
2 |
3 | public enum PlayState {
4 | prepare, start, pause, complete
5 | }
6 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/PlayUtilsPlus.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.utils;
2 |
3 | import android.media.MediaPlayer;
4 |
5 |
6 | public class PlayUtilsPlus {
7 | PlayStateChangeListener playStateChangeListener;
8 | MediaPlayer player;
9 |
10 | public PlayUtilsPlus() {
11 | }
12 |
13 | public void setPlayStateChangeListener(PlayStateChangeListener listener) {
14 | this.playStateChangeListener = listener;
15 | // this.playStateChangeListener.onPlayStateChange(PlayState.prepare);
16 | }
17 |
18 | public void startPlaying(String filePath) {
19 | try {
20 | isPause=false;
21 | this.player = new MediaPlayer();
22 | this.player.setDataSource(filePath);
23 | this.player.prepareAsync();
24 | this.player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
25 | public void onPrepared(MediaPlayer mp) {
26 | PlayUtilsPlus.this.player.start();
27 | }
28 | });
29 | if (this.playStateChangeListener != null) {
30 | // this.playStateChangeListener.onPlayStateChange(PlayState.start);
31 | }
32 |
33 | this.player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
34 | public void onCompletion(MediaPlayer mp) {
35 | PlayUtilsPlus.this.stopPlaying();
36 | }
37 | });
38 | } catch (Exception var3) {
39 | var3.printStackTrace();
40 | }
41 |
42 | }
43 | Boolean isPause = false;
44 |
45 | public boolean pausePlay() {
46 | try {
47 | if (this.player.isPlaying() && !isPause) {
48 | this.player.pause();
49 | isPause = true;
50 | } else {
51 | this.player.start();
52 | isPause = false;
53 | }
54 |
55 | } catch (Exception var2) {
56 | var2.printStackTrace();
57 | }
58 | return isPause ;
59 | }
60 |
61 | public void stopPlaying() {
62 | try {
63 | if (this.player != null) {
64 | this.player.stop();
65 | this.player.reset();
66 | this.player=null;
67 | if (this.playStateChangeListener != null) {
68 | this.playStateChangeListener.onPlayStateChange(PlayState.complete);
69 | }
70 | }
71 | } catch (Exception var2) {
72 | var2.printStackTrace();
73 | }
74 |
75 | }
76 |
77 | public boolean isPlaying() {
78 | try {
79 | return this.player != null && this.player.isPlaying();
80 | } catch (Exception var2) {
81 | return false;
82 | }
83 | }
84 |
85 | public interface PlayStateChangeListener {
86 | void onPlayStateChange(PlayState playState);
87 | }
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record/utils/RecorderUtil.java:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record.utils;
2 |
3 | import android.os.Environment;
4 | import android.util.Log;
5 |
6 | import com.maple.recorder.recording.AudioChunk;
7 | import com.maple.recorder.recording.AudioRecordConfig;
8 | import com.maple.recorder.recording.MsRecorder;
9 | import com.maple.recorder.recording.PullTransport;
10 | import com.maple.recorder.recording.Recorder;
11 |
12 | import java.io.File;
13 | import java.util.Date;
14 |
15 |
16 | public class RecorderUtil {
17 |
18 | Recorder recorder;
19 | public static String rootPath = "/yun_ke_fu/flutter/wav_file/";
20 | String voicePath;
21 | PlayUtilsPlus playUtils;
22 |
23 | RecordListener recordListener;
24 | PlayStateListener playStateListener;
25 |
26 | public RecorderUtil() {
27 | initVoice();
28 | }
29 |
30 |
31 |
32 | public RecorderUtil(String path) {
33 | voicePath =path;
34 | }
35 |
36 |
37 |
38 |
39 |
40 | public void addPlayAmplitudeListener(RecordListener recordListener) {
41 | this.recordListener = recordListener;
42 | }
43 | public void addPlayStateListener(PlayStateListener playStateListener) {
44 | this.playStateListener = playStateListener;
45 | }
46 |
47 | private void initVoice() {
48 | initPath();
49 | initVoicePath();
50 | initRecorder();
51 | }
52 |
53 |
54 |
55 | //初始化存储路径
56 | private void initPath() {
57 | String ROOT = "";// /storage/emulated/0
58 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
59 | ROOT = Environment.getExternalStorageDirectory().getPath();
60 | Log.e("voice", "系统方法:" + ROOT);
61 | }
62 | rootPath = ROOT + rootPath;
63 |
64 | File lrcFile = new File(rootPath);
65 | if (!lrcFile.exists()) {
66 | lrcFile.mkdirs();
67 | }
68 |
69 | Log.e("voice", "初始存储路径" + rootPath);
70 | }
71 |
72 |
73 | private void initVoicePath() {
74 | String forDate = DateUtils.dateToString(new Date());
75 | String name = "wav-" + forDate;
76 | voicePath = rootPath + name + ".wav";
77 | Log.e("voice", "初始化语音路径" + voicePath);
78 |
79 |
80 | }
81 |
82 |
83 | private void initRecorder() {
84 | recorder = MsRecorder.wav(
85 | new File(voicePath),
86 | new AudioRecordConfig.Default(),
87 | new PullTransport.Default()
88 | .setOnAudioChunkPulledListener(new PullTransport.OnAudioChunkPulledListener() {
89 | @Override
90 | public void onAudioChunkPulled(AudioChunk audioChunk) {
91 | if (recordListener != null) {
92 | recordListener.onPlayAmplitude(audioChunk.maxAmplitude());
93 | }
94 | }
95 | })
96 |
97 | );
98 | }
99 |
100 | public void startRecord() {
101 | if (recordListener != null) {
102 | recordListener.onVoicePathSuccess(voicePath);
103 | }
104 |
105 | recorder.stopRecording();
106 | recorder.startRecording();
107 | }
108 |
109 | public void stopRecord() {
110 | recorder.stopRecording();
111 | }
112 |
113 | public void playVoice() {
114 | if (playUtils == null) {
115 | playUtils = new PlayUtilsPlus();
116 | playUtils.setPlayStateChangeListener(new PlayUtilsPlus.PlayStateChangeListener() {
117 | @Override
118 | public void onPlayStateChange(PlayState playState) {
119 | playStateListener.playState(playState);
120 | }
121 | });
122 | }
123 | if(playUtils.isPlaying()) {
124 | playUtils.stopPlaying();
125 | }
126 | playUtils.startPlaying(voicePath);
127 | }
128 |
129 | public boolean pausePlay(){
130 | LogUtils.LOGD("wilson","pausePlay");
131 | boolean isPlaying = playUtils.pausePlay();
132 | return isPlaying;
133 | }
134 |
135 | public void stopPlay(){
136 | LogUtils.LOGD("wilson","stopPlay");
137 | playUtils.stopPlaying();
138 | }
139 |
140 | public interface RecordListener {
141 | void onPlayAmplitude(Double amplitude);
142 |
143 | void onVoicePathSuccess(String voicePath);
144 |
145 | }
146 |
147 | public interface PlayStateListener {
148 |
149 | void playState(PlayState playState);
150 |
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/example/.flutter-plugins-dependencies:
--------------------------------------------------------------------------------
1 | {"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"}
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | /build/
31 |
32 | # Android related
33 | **/android/**/gradle-wrapper.jar
34 | **/android/.gradle
35 | **/android/captures/
36 | **/android/gradlew
37 | **/android/gradlew.bat
38 | **/android/local.properties
39 | **/android/**/GeneratedPluginRegistrant.java
40 |
41 | # iOS/XCode related
42 | **/ios/**/*.mode1v3
43 | **/ios/**/*.mode2v3
44 | **/ios/**/*.moved-aside
45 | **/ios/**/*.pbxuser
46 | **/ios/**/*.perspectivev3
47 | **/ios/**/*sync/
48 | **/ios/**/.sconsign.dblite
49 | **/ios/**/.tags*
50 | **/ios/**/.vagrant/
51 | **/ios/**/DerivedData/
52 | **/ios/**/Icon?
53 | **/ios/**/Pods/
54 | **/ios/**/.symlinks/
55 | **/ios/**/profile
56 | **/ios/**/xcuserdata
57 | **/ios/.generated/
58 | **/ios/Flutter/App.framework
59 | **/ios/Flutter/Flutter.framework
60 | **/ios/Flutter/Generated.xcconfig
61 | **/ios/Flutter/app.flx
62 | **/ios/Flutter/app.zip
63 | **/ios/Flutter/flutter_assets/
64 | **/ios/Flutter/flutter_export_environment.sh
65 | **/ios/ServiceDefinitions.json
66 | **/ios/Runner/GeneratedPluginRegistrant.*
67 |
68 | # Exceptions to above rules.
69 | !**/ios/**/default.mode1v3
70 | !**/ios/**/default.mode2v3
71 | !**/ios/**/default.pbxuser
72 | !**/ios/**/default.perspectivev3
73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
74 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 68587a0916366e9512a78df22c44163d041dd5f3
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_plugin_record_example
2 |
3 |
4 | ### 使用
5 |
6 | ### 1, 初始化录制
7 |
8 |
9 |
10 | 可以在页面初始化的时候进行初始化比如: 在initState方法中进行初始化
11 |
12 | //实例化对象
13 | FlutterPluginRecord recordPlugin = new FlutterPluginRecord();
14 | // 初始化
15 | recordPlugin.init()
16 |
17 | ### 2, 开始录制
18 |
19 | recordPlugin.start()
20 | ### 3, 停止录制
21 | recordPlugin.stop()
22 | ### 4, 播放 暂停,停止播放
23 | #### 1,播放
24 |
25 | recordPlugin.play()
26 |
27 | #### 2, 暂停和继续播放
28 |
29 | recordPlugin.pausePlay();
30 |
31 | #### 3, 停止播放
32 |
33 | recordPlugin.stopPlay();
34 |
35 | ### 3, 释放资源
36 | 可以在页面退出的时候进行资源释放 比如在 dispose方法中调用如下代码
37 |
38 | recordPlugin.dispose()
39 |
40 | ### 4,回调监听
41 | 1,初始化回调监听
42 |
43 |
44 | ///初始化方法的监听
45 | recordPlugin.responseFromInit.listen((data) {
46 | if (data) {
47 | print("初始化成功");
48 | } else {
49 | print("初始化失败");
50 | }
51 | });
52 |
53 |
54 | 2,开始录制停止录制监听
55 |
56 | /// 开始录制或结束录制的监听
57 | recordPlugin.response.listen((data) {
58 | if (data.msg == "onStop") {
59 | ///结束录制时会返回录制文件的地址方便上传服务器
60 | print("onStop " + data.path);
61 | } else if (data.msg == "onStart") {
62 | print("onStart --");
63 | }
64 | });
65 |
66 | 3,录制声音大小回调监听
67 |
68 |
69 | ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
70 | recordPlugin.responseFromAmplitude.listen((data) {
71 | var voiceData = double.parse(data.msg);
72 | var tempVoice = "";
73 | if (voiceData > 0 && voiceData < 0.1) {
74 | tempVoice = "images/voice_volume_2.png";
75 | } else if (voiceData > 0.2 && voiceData < 0.3) {
76 | tempVoice = "images/voice_volume_3.png";
77 | } else if (voiceData > 0.3 && voiceData < 0.4) {
78 | tempVoice = "images/voice_volume_4.png";
79 | } else if (voiceData > 0.4 && voiceData < 0.5) {
80 | tempVoice = "images/voice_volume_5.png";
81 | } else if (voiceData > 0.5 && voiceData < 0.6) {
82 | tempVoice = "images/voice_volume_6.png";
83 | } else if (voiceData > 0.6 && voiceData < 0.7) {
84 | tempVoice = "images/voice_volume_7.png";
85 | } else if (voiceData > 0.7 && voiceData < 1) {
86 | tempVoice = "images/voice_volume_7.png";
87 | }
88 | setState(() {
89 | voiceIco = tempVoice;
90 | if(overlayEntry!=null){
91 | overlayEntry.markNeedsBuild();
92 | }
93 | });
94 |
95 | print("振幅大小 " + voiceData.toString() + " " + voiceIco);
96 | });
97 |
98 |
99 | ## 2,录制组件的使用
100 |
101 |
102 | ### 1,在使用的页面进行导入package
103 |
104 | import 'package:flutter_plugin_record/index.dart';
105 |
106 |
107 |
108 |
109 |
110 | ### 2,在使用的地方引入VoiceWidget组件
111 |
112 | new VoiceWidget(),
113 |
114 |
115 |
116 | ## TODO
117 |
118 | * [x] 实现发送语音时间按下抬起时间很短提示
119 | * [x] 优化代码
120 | * [x] 实现录制完成文件路径回调功能,方面使用者可以把录音文件上传服务器
121 |
122 |
123 | ## 关注公众号获取更多内容
124 |
125 | 
126 |
127 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 29
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "record.wilson.flutter.com.flutter_plugin_record_example"
42 | minSdkVersion 19
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 | }
48 |
49 | buildTypes {
50 | release {
51 | // TODO: Add your own signing config for the release build.
52 | // Signing with the debug keys for now, so `flutter run --release` works.
53 | signingConfig signingConfigs.debug
54 | }
55 | }
56 | }
57 |
58 | flutter {
59 | source '../..'
60 | }
61 |
62 | dependencies {
63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
64 | testImplementation 'junit:junit:4.12'
65 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
67 | }
68 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
17 |
20 |
27 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/record/wilson/flutter/com/flutter_plugin_record_example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package record.wilson.flutter.com.flutter_plugin_record_example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | // ext.kotlin_version = '1.2.71'
3 | ext.kotlin_version = '1.3.50'
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 |
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.5.3'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | jcenter()
20 | }
21 | }
22 |
23 | rootProject.buildDir = '../build'
24 | subprojects {
25 | project.buildDir = "${rootProject.buildDir}/${project.name}"
26 | }
27 | subprojects {
28 | project.evaluationDependsOn(':app')
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
3 | android.enableR8=true
4 | android.useAndroidX=true
5 | android.enableJetifier=true
6 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 18 09:02:45 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/res/values/strings_en.arb:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | dcb78416cfbfe60288e8c774d67c139c
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Flutter.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # NOTE: This podspec is NOT to be published. It is only used as a local source!
3 | # This is a generated file; do not edit or check into version control.
4 | #
5 |
6 | Pod::Spec.new do |s|
7 | s.name = 'Flutter'
8 | s.version = '1.0.0'
9 | s.summary = 'High-performance, high-fidelity mobile apps.'
10 | s.homepage = 'https://flutter.io'
11 | s.license = { :type => 'MIT' }
12 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
13 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
14 | s.ios.deployment_target = '8.0'
15 | # Framework linking is handled by Flutter tooling, not CocoaPods.
16 | # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
17 | s.vendored_frameworks = 'path/to/nothing'
18 | end
19 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_plugin_record (0.0.1):
4 | - Flutter
5 | - path_provider (0.0.1):
6 | - Flutter
7 | - shared_preferences (0.0.1):
8 | - Flutter
9 |
10 | DEPENDENCIES:
11 | - Flutter (from `Flutter`)
12 | - flutter_plugin_record (from `.symlinks/plugins/flutter_plugin_record/ios`)
13 | - path_provider (from `.symlinks/plugins/path_provider/ios`)
14 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
15 |
16 | EXTERNAL SOURCES:
17 | Flutter:
18 | :path: Flutter
19 | flutter_plugin_record:
20 | :path: ".symlinks/plugins/flutter_plugin_record/ios"
21 | path_provider:
22 | :path: ".symlinks/plugins/path_provider/ios"
23 | shared_preferences:
24 | :path: ".symlinks/plugins/shared_preferences/ios"
25 |
26 | SPEC CHECKSUMS:
27 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
28 | flutter_plugin_record: 562ded56f3a109d769e72c3ef52ef20d835493d4
29 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
30 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
31 |
32 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
33 |
34 | COCOAPODS: 1.10.1
35 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSAppTransportSecurity
6 |
7 | NSAllowsArbitraryLoads
8 |
9 |
10 | CFBundleDevelopmentRegion
11 | $(DEVELOPMENT_LANGUAGE)
12 | CFBundleExecutable
13 | $(EXECUTABLE_NAME)
14 | CFBundleIdentifier
15 | $(PRODUCT_BUNDLE_IDENTIFIER)
16 | CFBundleInfoDictionaryVersion
17 | 6.0
18 | CFBundleName
19 | flutter_plugin_record_example
20 | CFBundlePackageType
21 | APPL
22 | CFBundleShortVersionString
23 | $(FLUTTER_BUILD_NAME)
24 | CFBundleSignature
25 | ????
26 | CFBundleVersion
27 | $(CURRENT_PROJECT_VERSION)
28 | LSRequiresIPhoneOS
29 |
30 | NSMicrophoneUsageDescription
31 | 打开话筒
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UIMainStoryboardFile
35 | Main
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UIViewControllerBasedStatusBarAppearance
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/example/lib/generated/i18n.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | // ignore_for_file: non_constant_identifier_names
7 | // ignore_for_file: camel_case_types
8 | // ignore_for_file: prefer_single_quotes
9 |
10 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost.
11 | class S implements WidgetsLocalizations {
12 | const S();
13 |
14 | static S current;
15 |
16 | static const GeneratedLocalizationsDelegate delegate =
17 | GeneratedLocalizationsDelegate();
18 |
19 | static S of(BuildContext context) => Localizations.of(context, S);
20 |
21 | @override
22 | TextDirection get textDirection => TextDirection.ltr;
23 |
24 | }
25 |
26 | class $en extends S {
27 | const $en();
28 | }
29 |
30 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate {
31 | const GeneratedLocalizationsDelegate();
32 |
33 | List get supportedLocales {
34 | return const [
35 | Locale("en", ""),
36 | ];
37 | }
38 |
39 | LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) {
40 | return (List locales, Iterable supported) {
41 | if (locales == null || locales.isEmpty) {
42 | return fallback ?? supported.first;
43 | } else {
44 | return _resolve(locales.first, fallback, supported, withCountry);
45 | }
46 | };
47 | }
48 |
49 | LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) {
50 | return (Locale locale, Iterable supported) {
51 | return _resolve(locale, fallback, supported, withCountry);
52 | };
53 | }
54 |
55 | @override
56 | Future load(Locale locale) {
57 | final String lang = getLang(locale);
58 | if (lang != null) {
59 | switch (lang) {
60 | case "en":
61 | S.current = const $en();
62 | return SynchronousFuture(S.current);
63 | default:
64 | // NO-OP.
65 | }
66 | }
67 | S.current = const S();
68 | return SynchronousFuture(S.current);
69 | }
70 |
71 | @override
72 | bool isSupported(Locale locale) => _isSupported(locale, true);
73 |
74 | @override
75 | bool shouldReload(GeneratedLocalizationsDelegate old) => false;
76 |
77 | ///
78 | /// Internal method to resolve a locale from a list of locales.
79 | ///
80 | Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) {
81 | if (locale == null || !_isSupported(locale, withCountry)) {
82 | return fallback ?? supported.first;
83 | }
84 |
85 | final Locale languageLocale = Locale(locale.languageCode, "");
86 | if (supported.contains(locale)) {
87 | return locale;
88 | } else if (supported.contains(languageLocale)) {
89 | return languageLocale;
90 | } else {
91 | final Locale fallbackLocale = fallback ?? supported.first;
92 | return fallbackLocale;
93 | }
94 | }
95 |
96 | ///
97 | /// Returns true if the specified locale is supported, false otherwise.
98 | ///
99 | bool _isSupported(Locale locale, bool withCountry) {
100 | if (locale != null) {
101 | for (Locale supportedLocale in supportedLocales) {
102 | // Language must always match both locales.
103 | if (supportedLocale.languageCode != locale.languageCode) {
104 | continue;
105 | }
106 |
107 | // If country code matches, return this locale.
108 | if (supportedLocale.countryCode == locale.countryCode) {
109 | return true;
110 | }
111 |
112 | // If no country requirement is requested, check if this locale has no country.
113 | if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) {
114 | return true;
115 | }
116 | }
117 | }
118 | return false;
119 | }
120 | }
121 |
122 | String getLang(Locale l) => l == null
123 | ? null
124 | : l.countryCode != null && l.countryCode.isEmpty
125 | ? l.languageCode
126 | : l.toString();
127 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_plugin_record_example/path_provider_screen.dart';
3 | import 'package:flutter_plugin_record_example/record_mp3_screen.dart';
4 | import 'package:flutter_plugin_record_example/record_screen.dart';
5 | import 'package:flutter_plugin_record_example/wechat_record_screen.dart';
6 |
7 | void main() => runApp(MyApp());
8 |
9 | class MyApp extends StatelessWidget {
10 | @override
11 | Widget build(BuildContext context) {
12 | return new MaterialApp(
13 | title: 'Flutter Demo',
14 | theme: new ThemeData(
15 | primarySwatch: Colors.blue,
16 | ),
17 | home: new MyHomePage(title: 'Flutter Demo Home Page'),
18 | routes: {
19 | "RecordScreen": (BuildContext context) => new RecordScreen(),
20 | "RecordMp3Screen": (BuildContext context) => new RecordMp3Screen(),
21 | "WeChatRecordScreen": (BuildContext context) =>
22 | new WeChatRecordScreen(),
23 | "PathProviderScreen": (BuildContext context) =>
24 | new PathProviderScreen(),
25 | },
26 | );
27 | }
28 | }
29 |
30 | class MyHomePage extends StatefulWidget {
31 | MyHomePage({Key key, this.title}) : super(key: key);
32 | final String title;
33 |
34 | @override
35 | _MyHomePageState createState() => new _MyHomePageState();
36 | }
37 |
38 | class _MyHomePageState extends State {
39 | @override
40 | void initState() {
41 | super.initState();
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | return new Scaffold(
47 | appBar: new AppBar(
48 | title: new Text("flutter版微信语音录制实现"),
49 | ),
50 | body: new Center(
51 | child: new Column(
52 | mainAxisSize: MainAxisSize.min,
53 | children: [
54 | new FlatButton(
55 | onPressed: () {
56 | Navigator.pushNamed(context, "RecordScreen");
57 | },
58 | child: new Text("进入语音录制界面")),
59 | new FlatButton(
60 | onPressed: () {
61 | Navigator.pushNamed(context, "RecordMp3Screen");
62 | },
63 | child: new Text("进入录制mp3模式")),
64 | new FlatButton(
65 | onPressed: () {
66 | Navigator.pushNamed(context, "WeChatRecordScreen");
67 | },
68 | child: new Text("进入仿微信录制界面")),
69 | new FlatButton(
70 | onPressed: () {
71 | Navigator.pushNamed(context, "PathProviderScreen");
72 | },
73 | child: new Text("进入文件路径获取界面")),
74 | ],
75 | ),
76 | ),
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/example/lib/path_provider_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:path_provider/path_provider.dart';
5 |
6 | class PathProviderScreen extends StatefulWidget {
7 | PathProviderScreen({Key key, this.title}) : super(key: key);
8 | final String title;
9 |
10 | @override
11 | _PathProviderScreenState createState() => _PathProviderScreenState();
12 | }
13 |
14 | class _PathProviderScreenState extends State {
15 | Future _tempDirectory;
16 | Future _appSupportDirectory;
17 | Future _appLibraryDirectory;
18 | Future _appDocumentsDirectory;
19 | Future _externalDocumentsDirectory;
20 | Future> _externalStorageDirectories;
21 | Future> _externalCacheDirectories;
22 |
23 | void _requestTempDirectory() {
24 | setState(() {
25 | _tempDirectory = getTemporaryDirectory();
26 | });
27 | }
28 |
29 | Widget _buildDirectory(
30 | BuildContext context, AsyncSnapshot snapshot) {
31 | Text text = const Text('');
32 | if (snapshot.connectionState == ConnectionState.done) {
33 | if (snapshot.hasError) {
34 | text = Text('Error: ${snapshot.error}');
35 | } else if (snapshot.hasData) {
36 | text = Text('path: ${snapshot.data.path}');
37 | } else {
38 | text = const Text('path unavailable');
39 | }
40 | }
41 | return Padding(padding: const EdgeInsets.all(16.0), child: text);
42 | }
43 |
44 | Widget _buildDirectories(
45 | BuildContext context, AsyncSnapshot> snapshot) {
46 | Text text = const Text('');
47 | if (snapshot.connectionState == ConnectionState.done) {
48 | if (snapshot.hasError) {
49 | text = Text('Error: ${snapshot.error}');
50 | } else if (snapshot.hasData) {
51 | final String combined =
52 | snapshot.data.map((Directory d) => d.path).join(', ');
53 | text = Text('paths: $combined');
54 | } else {
55 | text = const Text('path unavailable');
56 | }
57 | }
58 | return Padding(padding: const EdgeInsets.all(16.0), child: text);
59 | }
60 |
61 | void _requestAppDocumentsDirectory() {
62 | setState(() {
63 | _appDocumentsDirectory = getApplicationDocumentsDirectory();
64 | });
65 | }
66 |
67 | void _requestAppSupportDirectory() {
68 | setState(() {
69 | _appSupportDirectory = getApplicationSupportDirectory();
70 | });
71 | }
72 |
73 | void _requestAppLibraryDirectory() {
74 | setState(() {
75 | _appLibraryDirectory = getLibraryDirectory();
76 | });
77 | }
78 |
79 | void _requestExternalStorageDirectory() {
80 | setState(() {
81 | _externalDocumentsDirectory = getExternalStorageDirectory();
82 | });
83 | }
84 |
85 | void _requestExternalStorageDirectories(StorageDirectory type) {
86 | setState(() {
87 | _externalStorageDirectories = getExternalStorageDirectories(type: type);
88 | });
89 | }
90 |
91 | void _requestExternalCacheDirectories() {
92 | setState(() {
93 | _externalCacheDirectories = getExternalCacheDirectories();
94 | });
95 | }
96 |
97 | @override
98 | Widget build(BuildContext context) {
99 | return Scaffold(
100 | appBar: AppBar(
101 | title: Text("获取文件路径界面"),
102 | ),
103 | body: Center(
104 | child: ListView(
105 | children: [
106 | Padding(
107 | padding: const EdgeInsets.all(16.0),
108 | child: RaisedButton(
109 | child: const Text('Get Temporary Directory'),
110 | onPressed: _requestTempDirectory,
111 | ),
112 | ),
113 | FutureBuilder(
114 | future: _tempDirectory, builder: _buildDirectory),
115 | Padding(
116 | padding: const EdgeInsets.all(16.0),
117 | child: RaisedButton(
118 | child: const Text('Get Application Documents Directory'),
119 | onPressed: _requestAppDocumentsDirectory,
120 | ),
121 | ),
122 | FutureBuilder(
123 | future: _appDocumentsDirectory, builder: _buildDirectory),
124 | Padding(
125 | padding: const EdgeInsets.all(16.0),
126 | child: RaisedButton(
127 | child: const Text('Get Application Support Directory'),
128 | onPressed: _requestAppSupportDirectory,
129 | ),
130 | ),
131 | FutureBuilder(
132 | future: _appSupportDirectory, builder: _buildDirectory),
133 | Padding(
134 | padding: const EdgeInsets.all(16.0),
135 | child: RaisedButton(
136 | child: const Text('Get Application Library Directory'),
137 | onPressed: _requestAppLibraryDirectory,
138 | ),
139 | ),
140 | FutureBuilder(
141 | future: _appLibraryDirectory, builder: _buildDirectory),
142 | Padding(
143 | padding: const EdgeInsets.all(16.0),
144 | child: RaisedButton(
145 | child: Text(
146 | '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directory"}'),
147 | onPressed:
148 | Platform.isIOS ? null : _requestExternalStorageDirectory,
149 | ),
150 | ),
151 | FutureBuilder(
152 | future: _externalDocumentsDirectory, builder: _buildDirectory),
153 | Column(children: [
154 | Padding(
155 | padding: const EdgeInsets.all(16.0),
156 | child: RaisedButton(
157 | child: Text(
158 | '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directories"}'),
159 | onPressed: Platform.isIOS
160 | ? null
161 | : () {
162 | _requestExternalStorageDirectories(
163 | StorageDirectory.music,
164 | );
165 | },
166 | ),
167 | ),
168 | ]),
169 | FutureBuilder>(
170 | future: _externalStorageDirectories,
171 | builder: _buildDirectories),
172 | Column(children: [
173 | Padding(
174 | padding: const EdgeInsets.all(16.0),
175 | child: RaisedButton(
176 | child: Text(
177 | '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Cache Directories"}'),
178 | onPressed:
179 | Platform.isIOS ? null : _requestExternalCacheDirectories,
180 | ),
181 | ),
182 | ]),
183 | FutureBuilder>(
184 | future: _externalCacheDirectories, builder: _buildDirectories),
185 | ],
186 | ),
187 | ),
188 | );
189 | }
190 | }
--------------------------------------------------------------------------------
/example/lib/record_mp3_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flustars/flustars.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_plugin_record/flutter_plugin_record.dart';
6 | import 'package:path_provider/path_provider.dart';
7 |
8 | class RecordMp3Screen extends StatefulWidget {
9 | @override
10 | _RecordMp3ScreenState createState() => _RecordMp3ScreenState();
11 | }
12 |
13 | class _RecordMp3ScreenState extends State {
14 | FlutterPluginRecord recordPlugin = new FlutterPluginRecord();
15 |
16 | String filePath = "";
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 |
22 | ///初始化方法的监听
23 | recordPlugin.responseFromInit.listen((data) {
24 | if (data) {
25 | print("初始化成功");
26 | } else {
27 | print("初始化失败");
28 | }
29 | });
30 |
31 | /// 开始录制或结束录制的监听
32 | recordPlugin.response.listen((data) {
33 | if (data.msg == "onStop") {
34 | ///结束录制时会返回录制文件的地址方便上传服务器
35 | print("onStop 文件路径" + data.path);
36 | filePath = data.path;
37 | print("onStop 时长 " + data.audioTimeLength.toString());
38 | } else if (data.msg == "onStart") {
39 | print("onStart --");
40 | } else {
41 | print("--" + data.msg);
42 | }
43 | });
44 |
45 | ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
46 | recordPlugin.responseFromAmplitude.listen((data) {
47 | var voiceData = double.parse(data.msg);
48 | print("振幅大小 " + voiceData.toString());
49 | });
50 |
51 | recordPlugin.responsePlayStateController.listen((data) {
52 | print("播放路径 " + data.playPath);
53 | print("播放状态 " + data.playState);
54 | });
55 | }
56 |
57 | @override
58 | Widget build(BuildContext context) {
59 | return Scaffold(
60 | appBar: AppBar(
61 | title: const Text('录制mp3'),
62 | ),
63 | body: Center(
64 | child: Column(
65 | children: [
66 | FlatButton(
67 | child: Text("初始化录制mp3"),
68 | onPressed: () {
69 | _initRecordMp3();
70 | },
71 | ),
72 | FlatButton(
73 | child: Text("开始录制"),
74 | onPressed: () {
75 | start();
76 | },
77 | ),
78 | FlatButton(
79 | child: Text("根据路径录制mp3文件"),
80 | onPressed: () {
81 | _requestAppDocumentsDirectory();
82 | },
83 | ),
84 | FlatButton(
85 | child: Text("停止录制"),
86 | onPressed: () {
87 | stop();
88 | },
89 | ),
90 | FlatButton(
91 | child: Text("播放"),
92 | onPressed: () {
93 | play();
94 | },
95 | ),
96 | FlatButton(
97 | child: Text("播放本地指定路径录音文件"),
98 | onPressed: () {
99 | playByPath(filePath,"file");
100 | },
101 | ),
102 | FlatButton(
103 | child: Text("播放网络mp3文件"),
104 | onPressed: () {
105 | playByPath("https://test-1259809289.cos.ap-nanjing.myqcloud.com/temp.mp3","url");
106 | },
107 | ),
108 | FlatButton(
109 | child: Text("暂停|继续播放"),
110 | onPressed: () {
111 | pause();
112 | },
113 | ),
114 | FlatButton(
115 | child: Text("停止播放"),
116 | onPressed: () {
117 | stopPlay();
118 | },
119 | ),
120 | ],
121 | ),
122 | ),
123 | );
124 | }
125 |
126 | void _requestAppDocumentsDirectory() {
127 | // if(Platform.isIOS){
128 | // //ios相关代码
129 | // setState(() {
130 | // getApplicationDocumentsDirectory().then((value) {
131 | // String nowDataTimeStr = DateUtil.getNowDateMs().toString();
132 | // String wavPath = value.path + "/" + nowDataTimeStr + ".wav";
133 | // startByWavPath(wavPath);
134 | // });
135 | // });
136 | // }else if(Platform.isAndroid){
137 | // //android相关代码
138 | // }
139 |
140 | setState(() {
141 | getApplicationDocumentsDirectory().then((value) {
142 | String nowDataTimeStr = DateUtil.getNowDateMs().toString();
143 | // TODO 注意IOS 传递的Mp3路径一定是以 .MP3 结尾
144 | String wavPath ="";
145 | if (Platform.isIOS) {
146 | wavPath = value.path + "/" + nowDataTimeStr+".MP3";
147 | }else{
148 | wavPath = value.path + "/" + nowDataTimeStr;
149 | }
150 | startByWavPath(wavPath);
151 | });
152 | });
153 | }
154 |
155 | ///初始化语音录制的方法
156 | void _initRecordMp3() async {
157 | recordPlugin.initRecordMp3();
158 | }
159 |
160 | ///开始语音录制的方法
161 | void start() async {
162 | recordPlugin.start();
163 | }
164 |
165 | ///根据传递的路径进行语音录制
166 | void startByWavPath(String wavPath) async {
167 | recordPlugin.startByWavPath(wavPath);
168 | }
169 |
170 | ///停止语音录制的方法
171 | void stop() {
172 | recordPlugin.stop();
173 | }
174 |
175 | ///播放语音的方法
176 | void play() {
177 | recordPlugin.play();
178 | }
179 |
180 | ///播放指定路径录音文件 url为iOS播放网络语音,file为播放本地语音文件
181 | void playByPath(String path,String type) {
182 | recordPlugin.playByPath(path,type);
183 | }
184 |
185 | ///暂停|继续播放
186 | void pause() {
187 | recordPlugin.pausePlay();
188 | }
189 |
190 | @override
191 | void dispose() {
192 | /// 当界面退出的时候是释放录音资源
193 | recordPlugin.dispose();
194 | super.dispose();
195 | }
196 |
197 | void stopPlay() {
198 | recordPlugin.stopPlay();
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/example/lib/record_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flustars/flustars.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_plugin_record/flutter_plugin_record.dart';
6 | import 'package:path_provider/path_provider.dart';
7 |
8 | class RecordScreen extends StatefulWidget {
9 | @override
10 | _RecordScreenState createState() => _RecordScreenState();
11 | }
12 |
13 | class _RecordScreenState extends State {
14 | FlutterPluginRecord recordPlugin = new FlutterPluginRecord();
15 |
16 | String filePath = "";
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 |
22 | ///初始化方法的监听
23 | recordPlugin.responseFromInit.listen((data) {
24 | if (data) {
25 | print("初始化成功");
26 | } else {
27 | print("初始化失败");
28 | }
29 | });
30 |
31 | /// 开始录制或结束录制的监听
32 | recordPlugin.response.listen((data) {
33 | if (data.msg == "onStop") {
34 | ///结束录制时会返回录制文件的地址方便上传服务器
35 | print("onStop 文件路径" + data.path);
36 | filePath = data.path;
37 | print("onStop 时长 " + data.audioTimeLength.toString());
38 | } else if (data.msg == "onStart") {
39 | print("onStart --");
40 | } else {
41 | print("--" + data.msg);
42 | }
43 | });
44 |
45 | ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
46 | recordPlugin.responseFromAmplitude.listen((data) {
47 | var voiceData = double.parse(data.msg);
48 | print("振幅大小 " + voiceData.toString());
49 | });
50 |
51 | recordPlugin.responsePlayStateController.listen((data) {
52 | print("播放路径 " + data.playPath);
53 | print("播放状态 " + data.playState);
54 | });
55 | }
56 |
57 | @override
58 | Widget build(BuildContext context) {
59 | return Scaffold(
60 | appBar: AppBar(
61 | title: const Text('录制wav'),
62 | ),
63 | body: Center(
64 | child: Column(
65 | children: [
66 | FlatButton(
67 | child: Text("初始化"),
68 | onPressed: () {
69 | _init();
70 | },
71 | ),
72 | FlatButton(
73 | child: Text("开始录制"),
74 | onPressed: () {
75 | start();
76 | },
77 | ),
78 | FlatButton(
79 | child: Text("根据路径录制wav文件"),
80 | onPressed: () {
81 | _requestAppDocumentsDirectory();
82 | },
83 | ),
84 | FlatButton(
85 | child: Text("停止录制"),
86 | onPressed: () {
87 | stop();
88 | },
89 | ),
90 | FlatButton(
91 | child: Text("播放"),
92 | onPressed: () {
93 | play();
94 | },
95 | ),
96 | FlatButton(
97 | child: Text("播放本地指定路径录音文件"),
98 | onPressed: () {
99 | playByPath(filePath,"file");
100 | },
101 | ),
102 | FlatButton(
103 | child: Text("播放网络wav文件"),
104 | onPressed: () {
105 | playByPath("https://test-1259809289.cos.ap-nanjing.myqcloud.com/test.wav","url");
106 | },
107 | ),
108 | FlatButton(
109 | child: Text("暂停|继续播放"),
110 | onPressed: () {
111 | pause();
112 | },
113 | ),
114 | FlatButton(
115 | child: Text("停止播放"),
116 | onPressed: () {
117 | stopPlay();
118 | },
119 | ),
120 | ],
121 | ),
122 | ),
123 | );
124 | }
125 |
126 | void _requestAppDocumentsDirectory() {
127 | // if(Platform.isIOS){
128 | // //ios相关代码
129 | // setState(() {
130 | // getApplicationDocumentsDirectory().then((value) {
131 | // String nowDataTimeStr = DateUtil.getNowDateMs().toString();
132 | // String wavPath = value.path + "/" + nowDataTimeStr + ".wav";
133 | // startByWavPath(wavPath);
134 | // });
135 | // });
136 | // }else if(Platform.isAndroid){
137 | // //android相关代码
138 | // }
139 |
140 | setState(() {
141 | getApplicationDocumentsDirectory().then((value) {
142 | String nowDataTimeStr = DateUtil.getNowDateMs().toString();
143 | String wavPath = value.path + "/" + nowDataTimeStr + ".wav";
144 | print(wavPath);
145 | startByWavPath(wavPath);
146 | });
147 | });
148 | }
149 |
150 | ///初始化语音录制的方法
151 | void _init() async {
152 | recordPlugin.init();
153 | }
154 |
155 | ///开始语音录制的方法
156 | void start() async {
157 | recordPlugin.start();
158 | }
159 |
160 | ///根据传递的路径进行语音录制
161 | void startByWavPath(String wavPath) async {
162 | recordPlugin.startByWavPath(wavPath);
163 | }
164 |
165 | ///停止语音录制的方法
166 | void stop() {
167 | recordPlugin.stop();
168 | }
169 |
170 | ///播放语音的方法
171 | void play() {
172 | recordPlugin.play();
173 | }
174 |
175 | ///播放指定路径录音文件 url为iOS播放网络语音,file为播放本地语音文件
176 | void playByPath(String path,String type) {
177 | recordPlugin.playByPath(path,type);
178 | }
179 |
180 | ///暂停|继续播放
181 | void pause() {
182 | recordPlugin.pausePlay();
183 | }
184 |
185 | @override
186 | void dispose() {
187 | /// 当界面退出的时候是释放录音资源
188 | recordPlugin.dispose();
189 | super.dispose();
190 | }
191 |
192 | void stopPlay() {
193 | recordPlugin.stopPlay();
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/example/lib/wechat_record_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_plugin_record/index.dart';
3 |
4 | class WeChatRecordScreen extends StatefulWidget {
5 | @override
6 | _WeChatRecordScreenState createState() => _WeChatRecordScreenState();
7 | }
8 |
9 | class _WeChatRecordScreenState extends State {
10 | String toastShow = "悬浮框";
11 | OverlayEntry overlayEntry;
12 |
13 | showView(BuildContext context) {
14 | if (overlayEntry == null) {
15 | overlayEntry = new OverlayEntry(builder: (content) {
16 | return Positioned(
17 | top: MediaQuery.of(context).size.height * 0.5 - 80,
18 | left: MediaQuery.of(context).size.width * 0.5 - 80,
19 | child: Material(
20 | child: Center(
21 | child: Opacity(
22 | opacity: 0.8,
23 | child: Container(
24 | width: 100,
25 | height: 100,
26 | decoration: BoxDecoration(
27 | color: Color(0xff77797A),
28 | borderRadius: BorderRadius.all(Radius.circular(20.0)),
29 | ),
30 | child: Column(
31 | children: [
32 | Container(
33 | // padding: EdgeInsets.only(right: 20, left: 20, top: 0),
34 | child: Text(
35 | toastShow,
36 | style: TextStyle(
37 | fontStyle: FontStyle.normal,
38 | color: Colors.white,
39 | fontSize: 14,
40 | ),
41 | ),
42 | )
43 | ],
44 | ),
45 | ),
46 | ),
47 | ),
48 | ),
49 | );
50 | });
51 | Overlay.of(context).insert(overlayEntry);
52 | }
53 | }
54 |
55 | startRecord() {
56 | print("开始录制");
57 | }
58 |
59 | stopRecord(String path, double audioTimeLength) {
60 | print("结束束录制");
61 | print("音频文件位置" + path);
62 | print("音频录制时长" + audioTimeLength.toString());
63 | }
64 |
65 | @override
66 | Widget build(BuildContext context) {
67 | return Scaffold(
68 | appBar: AppBar(
69 | title: Text("仿微信发送语音"),
70 | ),
71 | body: Container(
72 | child: Column(
73 | children: [
74 | new FlatButton(
75 | onPressed: () {
76 | showView(context);
77 | },
78 | child: new Text("悬浮组件")),
79 | new FlatButton(
80 | onPressed: () {
81 | if (overlayEntry != null) {
82 | overlayEntry.remove();
83 | overlayEntry = null;
84 | }
85 | },
86 | child: new Text("隐藏悬浮组件")),
87 | new FlatButton(
88 | onPressed: () {
89 | setState(() {
90 | toastShow = "111";
91 | if (overlayEntry != null) {
92 | overlayEntry.markNeedsBuild();
93 | }
94 | });
95 | },
96 | child: new Text("悬浮窗状态更新")),
97 | new VoiceWidget(
98 | startRecord: startRecord,
99 | stopRecord: stopRecord,
100 | // 加入定制化Container的相关属性
101 | height: 40.0,
102 | ),
103 | ],
104 | ),
105 | ),
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.5.0"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.2.0"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | common_utils:
47 | dependency: transitive
48 | description:
49 | name: common_utils
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "1.2.1"
53 | convert:
54 | dependency: transitive
55 | description:
56 | name: convert
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "2.1.1"
60 | crypto:
61 | dependency: transitive
62 | description:
63 | name: crypto
64 | url: "https://pub.flutter-io.cn"
65 | source: hosted
66 | version: "3.0.0"
67 | cupertino_icons:
68 | dependency: "direct main"
69 | description:
70 | name: cupertino_icons
71 | url: "https://pub.flutter-io.cn"
72 | source: hosted
73 | version: "0.1.3"
74 | decimal:
75 | dependency: transitive
76 | description:
77 | name: decimal
78 | url: "https://pub.flutter-io.cn"
79 | source: hosted
80 | version: "0.3.5"
81 | fake_async:
82 | dependency: transitive
83 | description:
84 | name: fake_async
85 | url: "https://pub.flutter-io.cn"
86 | source: hosted
87 | version: "1.2.0"
88 | ffi:
89 | dependency: transitive
90 | description:
91 | name: ffi
92 | url: "https://pub.flutter-io.cn"
93 | source: hosted
94 | version: "0.1.3"
95 | file:
96 | dependency: transitive
97 | description:
98 | name: file
99 | url: "https://pub.flutter-io.cn"
100 | source: hosted
101 | version: "5.2.1"
102 | flustars:
103 | dependency: "direct main"
104 | description:
105 | name: flustars
106 | url: "https://pub.flutter-io.cn"
107 | source: hosted
108 | version: "0.3.3"
109 | flutter:
110 | dependency: "direct main"
111 | description: flutter
112 | source: sdk
113 | version: "0.0.0"
114 | flutter_plugin_record:
115 | dependency: "direct dev"
116 | description:
117 | path: ".."
118 | relative: true
119 | source: path
120 | version: "1.0.0"
121 | flutter_test:
122 | dependency: "direct dev"
123 | description: flutter
124 | source: sdk
125 | version: "0.0.0"
126 | flutter_web_plugins:
127 | dependency: transitive
128 | description: flutter
129 | source: sdk
130 | version: "0.0.0"
131 | intl:
132 | dependency: transitive
133 | description:
134 | name: intl
135 | url: "https://pub.flutter-io.cn"
136 | source: hosted
137 | version: "0.16.1"
138 | js:
139 | dependency: transitive
140 | description:
141 | name: js
142 | url: "https://pub.flutter-io.cn"
143 | source: hosted
144 | version: "0.6.3"
145 | matcher:
146 | dependency: transitive
147 | description:
148 | name: matcher
149 | url: "https://pub.flutter-io.cn"
150 | source: hosted
151 | version: "0.12.10"
152 | meta:
153 | dependency: transitive
154 | description:
155 | name: meta
156 | url: "https://pub.flutter-io.cn"
157 | source: hosted
158 | version: "1.3.0"
159 | path:
160 | dependency: transitive
161 | description:
162 | name: path
163 | url: "https://pub.flutter-io.cn"
164 | source: hosted
165 | version: "1.8.0"
166 | path_provider:
167 | dependency: "direct main"
168 | description:
169 | name: path_provider
170 | url: "https://pub.flutter-io.cn"
171 | source: hosted
172 | version: "1.6.24"
173 | path_provider_linux:
174 | dependency: transitive
175 | description:
176 | name: path_provider_linux
177 | url: "https://pub.flutter-io.cn"
178 | source: hosted
179 | version: "0.0.1+2"
180 | path_provider_macos:
181 | dependency: transitive
182 | description:
183 | name: path_provider_macos
184 | url: "https://pub.flutter-io.cn"
185 | source: hosted
186 | version: "0.0.4+6"
187 | path_provider_platform_interface:
188 | dependency: transitive
189 | description:
190 | name: path_provider_platform_interface
191 | url: "https://pub.flutter-io.cn"
192 | source: hosted
193 | version: "1.0.4"
194 | path_provider_windows:
195 | dependency: transitive
196 | description:
197 | name: path_provider_windows
198 | url: "https://pub.flutter-io.cn"
199 | source: hosted
200 | version: "0.0.4+3"
201 | platform:
202 | dependency: transitive
203 | description:
204 | name: platform
205 | url: "https://pub.flutter-io.cn"
206 | source: hosted
207 | version: "2.2.1"
208 | plugin_platform_interface:
209 | dependency: transitive
210 | description:
211 | name: plugin_platform_interface
212 | url: "https://pub.flutter-io.cn"
213 | source: hosted
214 | version: "1.0.3"
215 | process:
216 | dependency: transitive
217 | description:
218 | name: process
219 | url: "https://pub.flutter-io.cn"
220 | source: hosted
221 | version: "3.0.13"
222 | rational:
223 | dependency: transitive
224 | description:
225 | name: rational
226 | url: "https://pub.flutter-io.cn"
227 | source: hosted
228 | version: "0.3.8"
229 | shared_preferences:
230 | dependency: transitive
231 | description:
232 | name: shared_preferences
233 | url: "https://pub.flutter-io.cn"
234 | source: hosted
235 | version: "0.5.12+4"
236 | shared_preferences_linux:
237 | dependency: transitive
238 | description:
239 | name: shared_preferences_linux
240 | url: "https://pub.flutter-io.cn"
241 | source: hosted
242 | version: "0.0.2+4"
243 | shared_preferences_macos:
244 | dependency: transitive
245 | description:
246 | name: shared_preferences_macos
247 | url: "https://pub.flutter-io.cn"
248 | source: hosted
249 | version: "0.0.1+11"
250 | shared_preferences_platform_interface:
251 | dependency: transitive
252 | description:
253 | name: shared_preferences_platform_interface
254 | url: "https://pub.flutter-io.cn"
255 | source: hosted
256 | version: "1.0.4"
257 | shared_preferences_web:
258 | dependency: transitive
259 | description:
260 | name: shared_preferences_web
261 | url: "https://pub.flutter-io.cn"
262 | source: hosted
263 | version: "0.1.2+7"
264 | shared_preferences_windows:
265 | dependency: transitive
266 | description:
267 | name: shared_preferences_windows
268 | url: "https://pub.flutter-io.cn"
269 | source: hosted
270 | version: "0.0.1+3"
271 | sky_engine:
272 | dependency: transitive
273 | description: flutter
274 | source: sdk
275 | version: "0.0.99"
276 | source_span:
277 | dependency: transitive
278 | description:
279 | name: source_span
280 | url: "https://pub.flutter-io.cn"
281 | source: hosted
282 | version: "1.8.0"
283 | sp_util:
284 | dependency: transitive
285 | description:
286 | name: sp_util
287 | url: "https://pub.flutter-io.cn"
288 | source: hosted
289 | version: "1.0.1"
290 | stack_trace:
291 | dependency: transitive
292 | description:
293 | name: stack_trace
294 | url: "https://pub.flutter-io.cn"
295 | source: hosted
296 | version: "1.10.0"
297 | stream_channel:
298 | dependency: transitive
299 | description:
300 | name: stream_channel
301 | url: "https://pub.flutter-io.cn"
302 | source: hosted
303 | version: "2.1.0"
304 | string_scanner:
305 | dependency: transitive
306 | description:
307 | name: string_scanner
308 | url: "https://pub.flutter-io.cn"
309 | source: hosted
310 | version: "1.1.0"
311 | synchronized:
312 | dependency: transitive
313 | description:
314 | name: synchronized
315 | url: "https://pub.flutter-io.cn"
316 | source: hosted
317 | version: "2.2.0+2"
318 | term_glyph:
319 | dependency: transitive
320 | description:
321 | name: term_glyph
322 | url: "https://pub.flutter-io.cn"
323 | source: hosted
324 | version: "1.2.0"
325 | test_api:
326 | dependency: transitive
327 | description:
328 | name: test_api
329 | url: "https://pub.flutter-io.cn"
330 | source: hosted
331 | version: "0.2.19"
332 | typed_data:
333 | dependency: transitive
334 | description:
335 | name: typed_data
336 | url: "https://pub.flutter-io.cn"
337 | source: hosted
338 | version: "1.3.0"
339 | uuid:
340 | dependency: transitive
341 | description:
342 | name: uuid
343 | url: "https://pub.flutter-io.cn"
344 | source: hosted
345 | version: "3.0.2"
346 | vector_math:
347 | dependency: transitive
348 | description:
349 | name: vector_math
350 | url: "https://pub.flutter-io.cn"
351 | source: hosted
352 | version: "2.1.0"
353 | win32:
354 | dependency: transitive
355 | description:
356 | name: win32
357 | url: "https://pub.flutter-io.cn"
358 | source: hosted
359 | version: "1.7.4"
360 | xdg_directories:
361 | dependency: transitive
362 | description:
363 | name: xdg_directories
364 | url: "https://pub.flutter-io.cn"
365 | source: hosted
366 | version: "0.1.0"
367 | sdks:
368 | dart: ">=2.12.0-29.10.beta <3.0.0"
369 | flutter: ">=1.12.13+hotfix.5"
370 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_plugin_record_example
2 | description: The flutter voice recording plug-in,provides the recording animation and the recording successfully returns to the recording file path
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.7.0 <3.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 |
28 | # The following adds the Cupertino Icons font to your application.
29 | # Use with the CupertinoIcons class for iOS style icons.
30 | cupertino_icons: ^0.1.3
31 | path_provider: ^1.6.8
32 | flustars: ^0.3.2
33 |
34 | dev_dependencies:
35 | flutter_test:
36 | sdk: flutter
37 |
38 | flutter_plugin_record:
39 | path: ../
40 |
41 | # For information on the generic Dart part of this file, see the
42 | # following page: https://dart.dev/tools/pub/pubspec
43 |
44 | # The following section is specific to Flutter.
45 | flutter:
46 |
47 | # The following line ensures that the Material Icons font is
48 | # included with your application, so that you can use the icons in
49 | # the material Icons class.
50 | uses-material-design: true
51 |
52 | # To add assets to your application, add an assets section, like this:
53 | # assets:
54 | # - images/a_dot_burr.jpeg
55 | # - images/a_dot_ham.jpeg
56 |
57 | # An image asset can refer to one or more resolution-specific "variants", see
58 | # https://flutter.dev/assets-and-images/#resolution-aware.
59 |
60 | # For details regarding adding assets from package dependencies, see
61 | # https://flutter.dev/assets-and-images/#from-packages
62 |
63 | # To add custom fonts to your application, add a fonts section here,
64 | # in this "flutter" section. Each entry in this list should have a
65 | # "family" key with the font family name, and a "fonts" key with a
66 | # list giving the asset and other descriptors for the font. For
67 | # example:
68 | # fonts:
69 | # - family: Schyler
70 | # fonts:
71 | # - asset: fonts/Schyler-Regular.ttf
72 | # - asset: fonts/Schyler-Italic.ttf
73 | # style: italic
74 | # - family: Trajan Pro
75 | # fonts:
76 | # - asset: fonts/TrajanPro.ttf
77 | # - asset: fonts/TrajanPro_Bold.ttf
78 | # weight: 700
79 | #
80 | # For details regarding fonts from package dependencies,
81 | # see https://flutter.dev/custom-fonts/#from-packages
82 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flutter_plugin_record_example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Verify Platform version', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that platform version is retrieved.
19 | expect(
20 | find.byWidgetPredicate(
21 | (Widget widget) => widget is Text &&
22 | widget.data.startsWith('Running on:'),
23 | ),
24 | findsOneWidget,
25 | );
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/flutter_plugin_record.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/images/voice_volume_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_1.png
--------------------------------------------------------------------------------
/images/voice_volume_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_2.png
--------------------------------------------------------------------------------
/images/voice_volume_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_3.png
--------------------------------------------------------------------------------
/images/voice_volume_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_4.png
--------------------------------------------------------------------------------
/images/voice_volume_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_5.png
--------------------------------------------------------------------------------
/images/voice_volume_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_6.png
--------------------------------------------------------------------------------
/images/voice_volume_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/images/voice_volume_7.png
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/Generated.xcconfig
37 | /Flutter/flutter_export_environment.sh
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/ios/Classes/DPAudioPlayer.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 | typedef void(^PlayCompleteBlock)(void);
5 |
6 | typedef void(^StartPlayingBlock)(BOOL isPlaying);
7 |
8 | @interface DPAudioPlayer : NSObject
9 |
10 | /**
11 | 播放完成回调
12 | */
13 | @property (nonatomic, copy) PlayCompleteBlock playComplete;
14 |
15 | /**
16 | 开始播放回调
17 | */
18 | @property (nonatomic, copy) StartPlayingBlock startPlaying;
19 |
20 | + (DPAudioPlayer *)sharedInstance;
21 |
22 | /**
23 | 播放data格式录音
24 |
25 | @param data 录音data
26 | */
27 | - (void)startPlayWithData:(NSData *)data;
28 |
29 | /**
30 | 停止播放
31 | */
32 | - (void)stopPlaying;
33 |
34 | /// 暂停播放
35 | - (bool)pausePlaying;
36 |
37 |
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/ios/Classes/DPAudioPlayer.m:
--------------------------------------------------------------------------------
1 |
2 | #import "DPAudioPlayer.h"
3 | #import
4 | #import
5 |
6 | @interface DPAudioPlayer ()
7 | {
8 | BOOL isPlaying;
9 | }
10 |
11 | @property (nonatomic, strong) AVAudioPlayer *audioPlayer;
12 |
13 | @end
14 |
15 | @implementation DPAudioPlayer
16 |
17 | static DPAudioPlayer *playerManager = nil;
18 | + (DPAudioPlayer *)sharedInstance
19 | {
20 | static dispatch_once_t onceToken;
21 |
22 | dispatch_once(&onceToken,^{
23 | playerManager = [[DPAudioPlayer alloc] init];
24 | });
25 | return playerManager;
26 | }
27 |
28 | - (instancetype)init
29 | {
30 | if (self) {
31 | //创建缓存录音文件到Tmp
32 | NSString *wavPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WAVtemporaryPlayer.wav"];
33 | NSString *amrPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AMRtemporaryPlayer.amr"];
34 |
35 | if (![[NSFileManager defaultManager]fileExistsAtPath:wavPlayerFilePath]) {
36 | [[NSData data] writeToFile:wavPlayerFilePath atomically:YES];
37 | }
38 | if (![[NSFileManager defaultManager]fileExistsAtPath:amrPlayerFilePath]) {
39 | [[NSData data] writeToFile:amrPlayerFilePath atomically:YES];
40 | }
41 |
42 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(proximityStateDidChange) name:UIDeviceProximityStateDidChangeNotification object:nil];
43 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
44 | [[AVAudioSession sharedInstance] setActive:YES error:nil];
45 |
46 | }
47 | return self;
48 | }
49 |
50 | - (void)startPlayWithData:(NSData *)data
51 | {
52 | // if (isPlaying) return;
53 | //打开红外传感器
54 | [[UIDevice currentDevice] setProximityMonitoringEnabled:YES];
55 | AVAudioSession *session = [AVAudioSession sharedInstance];
56 | [session setActive:true error:nil];
57 | [session setCategory:AVAudioSessionCategoryPlayback error:nil];
58 | // //默认情况下扬声器播放
59 | // AVAudioSessionPortOverride portOverride = AVAudioSessionPortOverrideNone;
60 | // [[AVAudioSession sharedInstance] overrideOutputAudioPort:portOverride error:nil];
61 |
62 | //self.audioPlayer = [[AVAudioPlayer alloc]initWithData:[self conversionAMRDataToWAVData:data] error:nil];
63 |
64 | if (isPlaying){
65 | [self.audioPlayer stop];
66 | self.audioPlayer = nil;
67 | isPlaying = NO;
68 | }
69 | self.audioPlayer = [[AVAudioPlayer alloc]initWithData:data error:nil];
70 | self.audioPlayer.meteringEnabled = YES;
71 | self.audioPlayer.delegate = self;
72 | self.audioPlayer.volume = 1.0;
73 | self.audioPlayer.numberOfLoops = 0;
74 | [self.audioPlayer prepareToPlay];
75 | [self.audioPlayer play];
76 |
77 | if ([self.audioPlayer isPlaying]) {
78 | isPlaying = YES;
79 | if (self.startPlaying) {
80 | self.startPlaying(YES);
81 | }
82 | } else {
83 | isPlaying = NO;
84 | if (self.startPlaying) {
85 | self.startPlaying(NO);
86 | }
87 | }
88 | }
89 |
90 | //暂停播放
91 | - (bool)pausePlaying
92 | {
93 | if (isPlaying){
94 | //关闭红外传感器
95 | [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
96 | [self.audioPlayer pause];
97 | isPlaying = NO;
98 | }else{
99 | [self.audioPlayer play];
100 | isPlaying = YES;
101 | }
102 |
103 | return isPlaying;
104 |
105 | }
106 |
107 |
108 | - (void)stopPlaying
109 | {
110 | if (!isPlaying) return;
111 | //关闭红外传感器
112 | [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
113 | [self.audioPlayer stop];
114 | self.audioPlayer = nil;
115 | isPlaying = NO;
116 | }
117 |
118 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
119 | {
120 | if (flag) {
121 | [self stopPlaying];
122 | if (self.playComplete) {
123 | self.playComplete();
124 | }
125 | }
126 | }
127 |
128 | - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer*)player error:(NSError *)error{
129 | //解码错误执行的动作
130 | NSLog(@"");
131 | }
132 |
133 | //- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player
134 | //{
135 | // isPlaying = NO;
136 | // [player stop];
137 | //}
138 |
139 | ////转换amr文件类型data为wav文件类型data
140 | //- (NSData *)conversionAMRDataToWAVData:(NSData *)amrData
141 | //{
142 | //
143 | // NSString *wavPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WAVtemporaryPlayer.wav"];
144 | // NSString *amrPlayerFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AMRtemporaryPlayer.amr"];
145 | //
146 | // //amr的data写入文件
147 | // [amrData writeToFile:amrPlayerFilePath atomically:YES];
148 | // //将AMR文件转码成WAVE文件
149 | // amr_file_to_wave_file([amrPlayerFilePath cStringUsingEncoding:NSUTF8StringEncoding],
150 | // [wavPlayerFilePath cStringUsingEncoding:NSUTF8StringEncoding]);
151 | //
152 | // //得到转码后wav的data
153 | // return [NSData dataWithContentsOfFile:wavPlayerFilePath];
154 | //}
155 |
156 | - (void)proximityStateDidChange
157 | {
158 | if ([UIDevice currentDevice].proximityState) {
159 | NSLog(@"有物品靠近");
160 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
161 | } else {
162 | NSLog(@"有物品离开");
163 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
164 | }
165 | }
166 |
167 |
168 | - (void)dealloc
169 | {
170 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceProximityStateDidChangeNotification object:nil];
171 | [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
172 | }
173 |
174 | @end
175 |
--------------------------------------------------------------------------------
/ios/Classes/DPAudioRecorder.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 | typedef void(^AudioRecorderFinishRecordingBlock)(NSData *data, NSTimeInterval audioTimeLength,NSString *path);
5 |
6 | typedef void(^AudioStartRecordingBlock)(BOOL isRecording);
7 |
8 | typedef void(^AudioRecordingFailBlock)(NSString *reason);
9 |
10 | typedef void(^AudioSpeakPowerBlock)(float power);
11 |
12 | /// 录制语音
13 | @interface DPAudioRecorder : NSObject
14 |
15 | /// 录制完成的回调
16 | @property (nonatomic, copy) AudioRecorderFinishRecordingBlock audioRecorderFinishRecording;
17 | /// 开始录制回调
18 | @property (nonatomic, copy) AudioStartRecordingBlock audioStartRecording;
19 | /// 录制失败回调
20 | @property (nonatomic, copy) AudioRecordingFailBlock audioRecordingFail;
21 | /// 音频值测量回调
22 | @property (nonatomic, copy) AudioSpeakPowerBlock audioSpeakPower;
23 |
24 |
25 | + (DPAudioRecorder *)sharedInstance;
26 |
27 | /// 传递录制文件路径
28 | - (void)initByWavPath:(NSString*) wavPath;
29 | - (void)initByMp3;
30 |
31 | /// 开始录音方法
32 | - (void)startRecording;
33 |
34 | /// 停止录音方法
35 | - (void)stopRecording;
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/ios/Classes/DPAudioRecorder.m:
--------------------------------------------------------------------------------
1 |
2 | #import "DPAudioRecorder.h"
3 | #import "DPAudioPlayer.h"
4 | #import
5 | #import
6 | #import "JX_GCDTimerManager.h"
7 |
8 |
9 |
10 | #define MAX_RECORDER_TIME 2100 //最大录制时间
11 | #define MIN_RECORDER_TIME 1 // 最小录制时间
12 |
13 | #define TimerName @"audioTimer_999"
14 |
15 | //定义音频枚举类型
16 | typedef NS_ENUM(NSUInteger, CSVoiceType) {
17 | CSVoiceTypeWav,
18 | CSVoiceTypeAmr
19 | };
20 |
21 | static const CSVoiceType preferredVoiceType = CSVoiceTypeWav;
22 |
23 |
24 | @interface DPAudioRecorder ()
25 | {
26 | BOOL isRecording;
27 | dispatch_source_t timer;
28 | NSTimeInterval __block audioTimeLength; //录音时长
29 | }
30 | @property (nonatomic, strong) AVAudioRecorder *audioRecorder;
31 | @property (nonatomic, strong) NSString *originWaveFilePath;
32 | @end
33 |
34 | @implementation DPAudioRecorder
35 |
36 | static DPAudioRecorder *recorderManager = nil;
37 |
38 |
39 | + (DPAudioRecorder *)sharedInstance
40 | {
41 | static dispatch_once_t onceToken;
42 |
43 | dispatch_once(&onceToken,^{
44 | recorderManager = [[DPAudioRecorder alloc] init];
45 | });
46 |
47 | return recorderManager;
48 | }
49 |
50 |
51 | /// 默认构造方法
52 | - (instancetype)init
53 | {
54 | if (self = [super init]) {
55 | //创建缓存录音文件到Tmp
56 | NSString *wavRecordFilePath = [self createWaveFilePath];
57 | if (![[NSFileManager defaultManager] fileExistsAtPath:wavRecordFilePath]) {
58 | [[NSData data] writeToFile:wavRecordFilePath atomically:YES];
59 | }
60 | self.originWaveFilePath = wavRecordFilePath;
61 |
62 | NSLog(@"ios------初始化默认录制文件路径---%@",wavRecordFilePath);
63 |
64 | }
65 | return self;
66 | }
67 |
68 |
69 | - (void) initByMp3{
70 | //创建缓存录音文件到Tmp
71 | NSString *mp3RecordFilePath = [self createMp3FilePath];
72 | if (![[NSFileManager defaultManager] fileExistsAtPath:mp3RecordFilePath]) {
73 | [[NSData data] writeToFile:mp3RecordFilePath atomically:YES];
74 | }
75 | self.originWaveFilePath = mp3RecordFilePath;
76 |
77 | NSLog(@"ios------初始化录制文件路径---%@",mp3RecordFilePath);
78 |
79 | }
80 |
81 | - (NSString *) createMp3FilePath {
82 | return [NSTemporaryDirectory() stringByAppendingPathComponent:@"WAVtemporaryRadio.MP3"];
83 |
84 | }
85 | - (NSString *) createWaveFilePath {
86 | return [NSTemporaryDirectory() stringByAppendingPathComponent:@"WAVtemporaryRadio.wav"];
87 |
88 | }
89 |
90 | /// 根据传递过来的文件路径创建wav录制文件路径
91 | /// @param wavPath 传递的文件路径
92 | - (void)initByWavPath:(NSString *) wavPath{
93 |
94 | NSString *wavRecordFilePath = wavPath;
95 | if (![[NSFileManager defaultManager] fileExistsAtPath:wavRecordFilePath]) {
96 | [[NSData data] writeToFile:wavRecordFilePath atomically:YES];
97 | }
98 |
99 | self.originWaveFilePath = wavRecordFilePath;
100 | NSLog(@"ios-----传递的录制文件路径-------- %@",wavRecordFilePath);
101 | }
102 |
103 | /// 开始录制方法
104 | - (void)startRecording
105 | {
106 | if (isRecording) return;
107 |
108 | [[DPAudioPlayer sharedInstance]stopPlaying];
109 | //开始录音
110 | [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:nil];
111 | // //默认情况下扬声器播放
112 | AVAudioSessionPortOverride portOverride = AVAudioSessionPortOverrideNone;
113 | [[AVAudioSession sharedInstance] overrideOutputAudioPort:portOverride error:nil];
114 |
115 | [[AVAudioSession sharedInstance] setActive:YES error:nil];
116 |
117 | [self.audioRecorder prepareToRecord];
118 |
119 | [self.audioRecorder record];
120 |
121 | if ([self.audioRecorder isRecording]) {
122 | isRecording = YES;
123 | [self activeTimer];
124 | if (self.audioStartRecording) {
125 | self.audioStartRecording(YES);
126 | }
127 | } else {
128 | if (self.audioStartRecording) {
129 | self.audioStartRecording(NO);
130 | }
131 | }
132 |
133 |
134 | [self createPickSpeakPowerTimer];
135 | }
136 |
137 | - (void)stopRecording;
138 | {
139 | if (!isRecording) return;
140 | // try!AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
141 | [self shutDownTimer];
142 | [self.audioRecorder stop];
143 | self.audioRecorder = nil;
144 |
145 | //设置播放语音为k公开模式
146 | AVAudioSession *avAudioSession = [AVAudioSession sharedInstance];
147 | [avAudioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
148 |
149 | }
150 |
151 | - (void)activeTimer
152 | {
153 |
154 | //录音时长
155 | audioTimeLength = 0;
156 | NSTimeInterval timeInterval = 0.1;
157 | __weak typeof(self) weakSelf = self;
158 | [[JX_GCDTimerManager sharedInstance] scheduledDispatchTimerWithName:TimerName timeInterval:timeInterval queue:nil repeats:YES actionOption:AbandonPreviousAction action:^{
159 | __strong typeof(weakSelf) strongSelf = weakSelf;
160 | strongSelf->audioTimeLength += timeInterval;
161 | if (strongSelf->audioTimeLength >= MAX_RECORDER_TIME) { //大于等于 MAX_RECORDER_TIME 秒停止
162 | [strongSelf stopRecording];
163 | }
164 | }];
165 | }
166 |
167 | - (void)shutDownTimer
168 | {
169 | [[JX_GCDTimerManager sharedInstance] cancelAllTimer];//定时器停止
170 | }
171 |
172 | - (AVAudioRecorder *)audioRecorder {
173 | if (!_audioRecorder) {
174 |
175 | //暂存录音文件路径
176 | NSString *wavRecordFilePath = self.originWaveFilePath;
177 | NSLog(@"%@", wavRecordFilePath);
178 | NSDictionary *param =
179 | @{AVSampleRateKey:@8000.0, //采样率
180 | AVFormatIDKey:@(kAudioFormatLinearPCM),//音频格式
181 | AVLinearPCMBitDepthKey:@16, //采样位数 默认 16
182 | AVNumberOfChannelsKey:@1, // 通道的数目
183 | AVEncoderAudioQualityKey:@(AVAudioQualityMin),
184 | AVEncoderBitRateKey:@16000,
185 | // AVEncoderBitRateStrategyKey:AVAudioBitRateStrategy_VariableConstrained
186 | };
187 |
188 | NSError *initError;
189 | NSURL *fileURL = [NSURL fileURLWithPath:wavRecordFilePath];
190 | _audioRecorder = [[AVAudioRecorder alloc] initWithURL:fileURL settings:param error:&initError];
191 | if (initError) {
192 | NSLog(@"AVAudioRecorder initError:%@", initError.localizedDescription);
193 | }
194 | _audioRecorder.delegate = self;
195 | _audioRecorder.meteringEnabled = YES;
196 | }
197 | return _audioRecorder;
198 | }
199 |
200 | #pragma mark - AVAudioRecorder
201 |
202 | - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
203 | {
204 | //暂存录音文件路径
205 | NSString *wavRecordFilePath = self.originWaveFilePath;
206 | NSLog(@"录音暂存位置 %@ ",wavRecordFilePath);
207 | NSData *cacheAudioData;
208 | switch (preferredVoiceType) {
209 | case CSVoiceTypeWav:
210 | cacheAudioData = [NSData dataWithContentsOfFile:wavRecordFilePath];
211 | break;
212 | }
213 |
214 | //大于最小录音时长时,发送数据
215 | if (audioTimeLength > MIN_RECORDER_TIME) {
216 | dispatch_async(dispatch_get_global_queue(0, 0), ^{
217 | NSUInteger location = 4100;
218 | NSData *body = [cacheAudioData subdataWithRange:NSMakeRange(location, cacheAudioData.length - location)];
219 | NSMutableData *data1 = WriteWavFileHeader(body.length + 44, 8000, 1, 16).mutableCopy;
220 | [data1 appendData:body];
221 | // NSLog(@"date1date1date1date1[0-200]:%@", [data1 subdataWithRange:NSMakeRange(0, 200)]);
222 |
223 | dispatch_sync(dispatch_get_main_queue(), ^{
224 | if (self.audioRecorderFinishRecording) {
225 | self.audioRecorderFinishRecording(data1, self->audioTimeLength,wavRecordFilePath);
226 | }
227 | });
228 | });
229 | } else {
230 | if (self.audioRecordingFail) {
231 | self.audioRecordingFail(@"录音时长小于设定最短时长");
232 | }
233 | }
234 |
235 | isRecording = NO;
236 |
237 | //取消定时器
238 | if (timer) {
239 | dispatch_source_cancel(timer);
240 | timer = NULL;
241 | }
242 | }
243 |
244 | NSData* WriteWavFileHeader(long lengthWithHeader, int sampleRate, int channels, int PCMBitDepth) {
245 | Byte header[44];
246 | header[0] = 'R'; // RIFF/WAVE header
247 | header[1] = 'I';
248 | header[2] = 'F';
249 | header[3] = 'F';
250 | long totalDataLen = lengthWithHeader - 8;
251 | header[4] = (Byte) (totalDataLen & 0xff); //file-size (equals file-size - 8)
252 | header[5] = (Byte) ((totalDataLen >> 8) & 0xff);
253 | header[6] = (Byte) ((totalDataLen >> 16) & 0xff);
254 | header[7] = (Byte) ((totalDataLen >> 24) & 0xff);
255 | header[8] = 'W'; // Mark it as type "WAVE"
256 | header[9] = 'A';
257 | header[10] = 'V';
258 | header[11] = 'E';
259 | header[12] = 'f'; // Mark the format section 'fmt ' chunk
260 | header[13] = 'm';
261 | header[14] = 't';
262 | header[15] = ' ';
263 | header[16] = 16; // 4 bytes: size of 'fmt ' chunk, Length of format data. Always 16
264 | header[17] = 0;
265 | header[18] = 0;
266 | header[19] = 0;
267 | header[20] = 1; // format = 1 ,Wave type PCM
268 | header[21] = 0;
269 | header[22] = (Byte) channels; // channels
270 | header[23] = 0;
271 | header[24] = (Byte) (sampleRate & 0xff);
272 | header[25] = (Byte) ((sampleRate >> 8) & 0xff);
273 | header[26] = (Byte) ((sampleRate >> 16) & 0xff);
274 | header[27] = (Byte) ((sampleRate >> 24) & 0xff);
275 | int byteRate = sampleRate * channels * PCMBitDepth >> 3;
276 | header[28] = (Byte) (byteRate & 0xff);
277 | header[29] = (Byte) ((byteRate >> 8) & 0xff);
278 | header[30] = (Byte) ((byteRate >> 16) & 0xff);
279 | header[31] = (Byte) ((byteRate >> 24) & 0xff);
280 | header[32] = (Byte) (channels * PCMBitDepth >> 3); // block align
281 | header[33] = 0;
282 | header[34] = PCMBitDepth; // bits per sample
283 | header[35] = 0;
284 | header[36] = 'd'; //"data" marker
285 | header[37] = 'a';
286 | header[38] = 't';
287 | header[39] = 'a';
288 | long totalAudioLen = lengthWithHeader - 44;
289 | header[40] = (Byte) (totalAudioLen & 0xff); //data-size (equals file-size - 44).
290 | header[41] = (Byte) ((totalAudioLen >> 8) & 0xff);
291 | header[42] = (Byte) ((totalAudioLen >> 16) & 0xff);
292 | header[43] = (Byte) ((totalAudioLen >> 24) & 0xff);
293 | return [[NSData alloc] initWithBytes:header length:44];;
294 | }
295 |
296 | //音频值测量
297 | - (void)createPickSpeakPowerTimer
298 | {
299 | timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
300 | dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
301 |
302 | __weak __typeof(self) weakSelf = self;
303 |
304 | dispatch_source_set_event_handler(timer, ^{
305 | __strong __typeof(weakSelf) _self = weakSelf;
306 |
307 | [_self->_audioRecorder updateMeters];
308 | double lowPassResults = pow(10, (0.05 * [_self->_audioRecorder averagePowerForChannel:0]));
309 | if (_self.audioSpeakPower) {
310 | _self.audioSpeakPower(lowPassResults);
311 | }
312 | });
313 |
314 | dispatch_resume(timer);
315 | }
316 |
317 | - (void)dealloc
318 | {
319 | if (isRecording) [self.audioRecorder stop];
320 | [[NSNotificationCenter defaultCenter] removeObserver:self];
321 | }
322 |
323 | @end
324 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterPluginRecordPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface FlutterPluginRecordPlugin : NSObject
4 | @end
5 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterPluginRecordPlugin.m:
--------------------------------------------------------------------------------
1 | #import "FlutterPluginRecordPlugin.h"
2 | #import "DPAudioRecorder.h"
3 | #import "DPAudioPlayer.h"
4 |
5 |
6 | @implementation FlutterPluginRecordPlugin{
7 | FlutterMethodChannel *_channel;
8 | FlutterResult _result;
9 | FlutterMethodCall *_call;
10 | NSData *wavData;
11 | NSString *audioPath;
12 | BOOL _isInit;//是否执行初始化的标识
13 | }
14 |
15 | + (void)registerWithRegistrar:(NSObject *)registrar {
16 | FlutterMethodChannel *channel = [FlutterMethodChannel
17 | methodChannelWithName:@"flutter_plugin_record"
18 | binaryMessenger:[registrar messenger]];
19 |
20 | FlutterPluginRecordPlugin *instance = [[FlutterPluginRecordPlugin alloc] initWithChannel:channel];
21 | [registrar addMethodCallDelegate:instance channel:channel];
22 | }
23 |
24 | - (instancetype)initWithChannel:(FlutterMethodChannel *)channel {
25 | self = [super init];
26 | if (self) {
27 | _channel = channel;
28 | _isInit = NO;
29 | }
30 | return self;
31 | }
32 |
33 | - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{
34 | _result = result;
35 | _call = call;
36 | NSString *method = call.method;
37 | if ([@"init" isEqualToString:method]) {
38 | [self initRecord ];
39 | }else if([@"initRecordMp3" isEqualToString:method]){
40 | [self initMp3Record];
41 | }else if([@"startByWavPath" isEqualToString:method]){
42 | [self startByWavPath];
43 | }else if([@"start" isEqualToString:method]){
44 | [self start ];
45 | }else if([@"stop" isEqualToString:method]){
46 | [self stop ];
47 | }else if([@"play" isEqualToString:method]){
48 | [self play ];
49 | }else if([@"pause" isEqualToString:method]){
50 | [self pausePlay ];
51 | }else if([@"playByPath" isEqualToString:method]){
52 | [self playByPath];
53 | }else if([@"stopPlay" isEqualToString:method]){
54 | [self stopPlay];
55 | }else{
56 | result(FlutterMethodNotImplemented);
57 | }
58 |
59 | }
60 |
61 |
62 |
63 | //初始化录制mp3
64 | - (void) initMp3Record{
65 | [DPAudioRecorder.sharedInstance initByMp3];
66 | [self initRecord];
67 |
68 | }
69 |
70 | ///初始化语音录制的方法 初始化录制完成的回调,开始录制的回调,录制失败的回调,录制音量大小的回调
71 | /// 注意未初始化的话 Flutter 不能监听到上述回调事件
72 | - (void) initRecord{
73 | _isInit = YES;
74 |
75 | DPAudioRecorder.sharedInstance.audioRecorderFinishRecording = ^void (NSData *data, NSTimeInterval audioTimeLength,NSString *path){
76 | self->audioPath =path;
77 | self->wavData = data;
78 | NSLog(@"ios voice onStop");
79 | NSDictionary *args = [self->_call arguments];
80 | NSString *mId = [args valueForKey:@"id"];
81 |
82 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:
83 | @"success", @"result",
84 | mId, @"id",
85 | path, @"voicePath",
86 | [NSString stringWithFormat:@"%.20lf", audioTimeLength], @"audioTimeLength",
87 | nil];
88 | [self->_channel invokeMethod:@"onStop" arguments:dict3];
89 |
90 | };
91 |
92 | DPAudioRecorder.sharedInstance.audioStartRecording = ^void(BOOL isRecording){
93 | NSLog(@"ios voice start audioStartRecording");
94 | };
95 | DPAudioRecorder.sharedInstance.audioRecordingFail = ^void(NSString *reason){
96 |
97 | NSLog(@"ios voice %@", reason);
98 |
99 | };
100 | DPAudioRecorder.sharedInstance.audioSpeakPower = ^void(float power){
101 | NSLog(@"ios voice %f",power);
102 | NSString *powerStr = [NSString stringWithFormat:@"%f", power];
103 | NSDictionary *args = [self->_call arguments];
104 | NSString *mId = [args valueForKey:@"id"];
105 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:
106 | @"success",@"result",
107 | mId ,@"id",
108 | powerStr,@"amplitude",
109 | nil];
110 | [self->_channel invokeMethod:@"onAmplitude" arguments:dict3];
111 | };
112 |
113 | NSLog(@"ios voice init");
114 | NSDictionary *args = [_call arguments];
115 | NSString *mId = [args valueForKey:@"id"];
116 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@"success",@"result",mId,@"id", nil];
117 | [_channel invokeMethod:@"onInit" arguments:dict3];
118 | }
119 |
120 |
121 |
122 | /// 开始录制的方法
123 | - (void) start{
124 | if (!_isInit) {
125 | NSLog(@"ios-------未初始化录制方法- initRecord--");
126 | return;
127 | }
128 | NSLog(@"ios--------start record -----function--- start----");
129 | [DPAudioRecorder.sharedInstance startRecording];
130 |
131 | NSDictionary *args = [_call arguments];
132 | NSString *mId = [args valueForKey:@"id"];
133 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@"success",@"result",mId,@"id", nil];
134 | [_channel invokeMethod:@"onStart" arguments:dict3];
135 | }
136 |
137 | /// 根据文件路径进行录制
138 | - (void) startByWavPath{
139 | if (!_isInit) {
140 | NSLog(@"ios-------未初始化录制方法- initRecord--");
141 | return;
142 | }
143 | NSDictionary *args = [_call arguments];
144 | NSString *mId = [args valueForKey:@"id"];
145 | NSString *wavPath = [args valueForKey:@"wavPath"];
146 |
147 | NSLog(@"ios--------start record -----function--- startByWavPath----%@", wavPath);
148 |
149 | [DPAudioRecorder.sharedInstance initByWavPath:wavPath];
150 | [DPAudioRecorder.sharedInstance startRecording];
151 |
152 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@"success",@"result",mId,@"id", nil];
153 | [_channel invokeMethod:@"onStart" arguments:dict3];
154 | }
155 |
156 |
157 |
158 | /// 停止录制的方法
159 | - (void) stop{
160 | if (!_isInit) {
161 | NSLog(@"ios-------未初始化录制方法- initRecord--");
162 | return;
163 | }
164 | NSLog(@"ios--------stop record -----function--- stop----");
165 | [DPAudioRecorder.sharedInstance stopRecording];
166 | }
167 |
168 |
169 |
170 | /// 播放录制完成的音频
171 | - (void) play{
172 |
173 | NSLog(@"ios------play voice by warData----function---play--");
174 | [DPAudioPlayer.sharedInstance startPlayWithData:self->wavData];
175 | DPAudioPlayer.sharedInstance.playComplete = ^void(){
176 | NSLog(@"ios-----播放完成----by play");
177 | NSDictionary *args = [self->_call arguments];
178 | NSString *mId = [args valueForKey:@"id"];
179 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:self->audioPath,@"playPath",@"complete",@"playState",mId,@"id", nil];
180 | [self->_channel invokeMethod:@"onPlayState" arguments:dict3];
181 | };
182 |
183 |
184 | NSDictionary *args = [_call arguments];
185 | NSString *mId = [args valueForKey:@"id"];
186 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@"success",@"result",mId,@"id", nil];
187 | [_channel invokeMethod:@"onPlay" arguments:dict3];
188 | }
189 | - (void)stopPlay{
190 | [DPAudioPlayer.sharedInstance stopPlaying];
191 |
192 | }
193 | - (void) pausePlay{
194 |
195 | NSLog(@"ios------pausePlay----function---pausePlay--");
196 | bool isPlaying = [DPAudioPlayer.sharedInstance pausePlaying];
197 |
198 | NSDictionary *args = [_call arguments];
199 | NSString *mId = [args valueForKey:@"id"];
200 | NSString *isPlayingStr = nil;
201 | if (isPlaying) {
202 | isPlayingStr = @"true";
203 | }else{
204 | isPlayingStr = @"false";
205 | }
206 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:
207 | @"success",@"result",
208 | isPlayingStr,@"isPlaying",
209 | mId,@"id",
210 | nil];
211 | [_channel invokeMethod:@"pausePlay" arguments:dict3];
212 | }
213 |
214 | /// 根据指定路径播放音频
215 | - (void) playByPath{
216 | NSLog(@"ios------play voice by path-----function---playByPath---");
217 | NSDictionary *args = [_call arguments];
218 | NSString *filePath = [args valueForKey:@"path"];
219 |
220 | NSString *typeStr = [args valueForKey:@"type"];
221 | NSData *data;
222 | if ([typeStr isEqualToString:@"url"]) {
223 | data =[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:filePath]];
224 | }else if([typeStr isEqualToString:@"file"]){
225 | data= [NSData dataWithContentsOfFile:filePath];
226 |
227 | }
228 |
229 | [DPAudioPlayer.sharedInstance startPlayWithData:data];
230 | DPAudioPlayer.sharedInstance.playComplete = ^void(){
231 | NSLog(@"ios-----播放完成----by playbyPath---");
232 | NSDictionary *args = [self->_call arguments];
233 | NSString *mId = [args valueForKey:@"id"];
234 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:filePath,@"playPath",@"complete",@"playState",mId,@"id", nil];
235 | [self->_channel invokeMethod:@"onPlayState" arguments:dict3];
236 | };
237 |
238 | NSString *mId = [args valueForKey:@"id"];
239 | NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:@"success",@"result",mId,@"id", nil];
240 | [_channel invokeMethod:@"onPlay" arguments:dict3];
241 | }
242 |
243 |
244 | @end
245 |
246 |
--------------------------------------------------------------------------------
/ios/Classes/JX_GCDTimerManager.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 | typedef enum : NSUInteger {
5 | AbandonPreviousAction, // 废除之前的任务
6 | MergePreviousAction // 将之前的任务合并到新的任务中
7 | } ActionOption;
8 |
9 | @interface JX_GCDTimerManager : NSObject
10 |
11 | + (JX_GCDTimerManager *)sharedInstance;
12 |
13 | /**
14 | 启动一个timer,默认精度为0.1秒
15 |
16 | @param timerName timer的名称,作为唯一标识
17 | @param interval 执行的时间间隔
18 | @param queue timer将被放入的队列,也就是最终action执行的队列。传入nil将自动放到一个子线程队列中
19 | @param repeats timer是否循环调用
20 | @param option 多次schedule同一个timer时的操作选项(目前提供将之前的任务废除或合并的选项)
21 | @param action 时间间隔到点时执行的block
22 | */
23 | - (void)scheduledDispatchTimerWithName:(NSString *)timerName
24 | timeInterval:(double)interval
25 | queue:(dispatch_queue_t)queue
26 | repeats:(BOOL)repeats
27 | actionOption:(ActionOption)option
28 | action:(dispatch_block_t)action;
29 |
30 | /**
31 | 撤销某个timer
32 |
33 | @param timerName timer的名称,作为唯一标识
34 | */
35 | - (void)cancelTimerWithName:(NSString *)timerName;
36 |
37 | /**
38 | 撤销所有的timer
39 | */
40 | - (void)cancelAllTimer;
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/ios/Classes/JX_GCDTimerManager.m:
--------------------------------------------------------------------------------
1 |
2 | #import "JX_GCDTimerManager.h"
3 |
4 | @interface JX_GCDTimerManager()
5 |
6 | @property (nonatomic, strong) NSMutableDictionary *timerContainer;
7 | @property (nonatomic, strong) NSMutableDictionary *actionBlockCache;
8 |
9 | @end
10 |
11 | @implementation JX_GCDTimerManager
12 |
13 | #pragma mark - Public Method
14 |
15 | + (JX_GCDTimerManager *)sharedInstance
16 | {
17 | static JX_GCDTimerManager *_gcdTimerManager = nil;
18 | static dispatch_once_t onceToken;
19 |
20 | dispatch_once(&onceToken,^{
21 | _gcdTimerManager = [[JX_GCDTimerManager alloc] init];
22 | });
23 |
24 | return _gcdTimerManager;
25 | }
26 |
27 | - (void)scheduledDispatchTimerWithName:(NSString *)timerName
28 | timeInterval:(double)interval
29 | queue:(dispatch_queue_t)queue
30 | repeats:(BOOL)repeats
31 | actionOption:(ActionOption)option
32 | action:(dispatch_block_t)action
33 | {
34 | if (nil == timerName)
35 | return;
36 |
37 | if (nil == queue)
38 | queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
39 |
40 | dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
41 | if (!timer) {
42 | timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
43 | dispatch_resume(timer);
44 | [self.timerContainer setObject:timer forKey:timerName];
45 | }
46 |
47 | /* timer精度为0.1秒 */
48 | dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
49 |
50 | __weak typeof(self) weakSelf = self;
51 |
52 | switch (option) {
53 |
54 | case AbandonPreviousAction:
55 | {
56 | /* 移除之前的action */
57 | [weakSelf removeActionCacheForTimer:timerName];
58 |
59 | dispatch_source_set_event_handler(timer, ^{
60 | action();
61 |
62 | if (!repeats) {
63 | [weakSelf cancelTimerWithName:timerName];
64 | }
65 | });
66 | }
67 | break;
68 |
69 | case MergePreviousAction:
70 | {
71 | /* cache本次的action */
72 | [self cacheAction:action forTimer:timerName];
73 |
74 | dispatch_source_set_event_handler(timer, ^{
75 | NSMutableArray *actionArray = [self.actionBlockCache objectForKey:timerName];
76 | [actionArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
77 | dispatch_block_t actionBlock = obj;
78 | actionBlock();
79 | }];
80 | [weakSelf removeActionCacheForTimer:timerName];
81 |
82 | if (!repeats) {
83 | [weakSelf cancelTimerWithName:timerName];
84 | }
85 | });
86 | }
87 | break;
88 | }
89 | }
90 |
91 | - (void)cancelTimerWithName:(NSString *)timerName
92 | {
93 | dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
94 |
95 | if (!timer) {
96 | return;
97 | }
98 |
99 | [self.timerContainer removeObjectForKey:timerName];
100 | dispatch_source_cancel(timer);
101 |
102 | [self.actionBlockCache removeObjectForKey:timerName];
103 | }
104 |
105 | - (void)cancelAllTimer
106 | {
107 | // Fast Enumeration
108 | [self.timerContainer enumerateKeysAndObjectsUsingBlock:^(NSString *timerName, dispatch_source_t timer, BOOL *stop) {
109 | [self.timerContainer removeObjectForKey:timerName];
110 | dispatch_source_cancel(timer);
111 | }];
112 | }
113 |
114 | #pragma mark - Property
115 |
116 | - (NSMutableDictionary *)timerContainer
117 | {
118 | if (!_timerContainer) {
119 | _timerContainer = [[NSMutableDictionary alloc] init];
120 | }
121 | return _timerContainer;
122 | }
123 |
124 | - (NSMutableDictionary *)actionBlockCache
125 | {
126 | if (!_actionBlockCache) {
127 | _actionBlockCache = [[NSMutableDictionary alloc] init];
128 | }
129 | return _actionBlockCache;
130 | }
131 |
132 | #pragma mark - Private Method
133 |
134 | - (void)cacheAction:(dispatch_block_t)action forTimer:(NSString *)timerName
135 | {
136 | id actionArray = [self.actionBlockCache objectForKey:timerName];
137 |
138 | if (actionArray && [actionArray isKindOfClass:[NSMutableArray class]]) {
139 | [(NSMutableArray *)actionArray addObject:action];
140 | }else {
141 | NSMutableArray *array = [NSMutableArray arrayWithObject:action];
142 | [self.actionBlockCache setObject:array forKey:timerName];
143 | }
144 | }
145 |
146 | - (void)removeActionCacheForTimer:(NSString *)timerName
147 | {
148 | if (![self.actionBlockCache objectForKey:timerName])
149 | return;
150 |
151 | [self.actionBlockCache removeObjectForKey:timerName];
152 | }
153 |
154 | @end
155 |
--------------------------------------------------------------------------------
/ios/flutter_plugin_record.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
3 | #
4 | #use_frameworks!
5 | Pod::Spec.new do |s|
6 | s.name = 'flutter_plugin_record'
7 | s.version = '0.0.1'
8 | s.summary = 'A new Flutter plugin.'
9 | s.description = <<-DESC
10 | A new Flutter plugin.
11 | DESC
12 | s.homepage = 'http://example.com'
13 | s.license = { :file => '../LICENSE' }
14 | s.author = { 'Your Company' => 'email@example.com' }
15 | s.source = { :path => '.' }
16 | # s.swift_version = '4.2'
17 | s.source_files = 'Classes/**/*.{h,m}'
18 | s.public_header_files = 'Classes/**/*.h'
19 | # s.vendored_libraries = 'Classes/libopencore-amrnb.a'
20 | s.dependency 'Flutter'
21 | s.framework = "AVFoundation"
22 | s.ios.deployment_target = '8.0'
23 | end
24 |
25 |
--------------------------------------------------------------------------------
/lib/const/play_state.dart:
--------------------------------------------------------------------------------
1 | class PlayState {
2 | String playState;
3 | String playPath;
4 |
5 | PlayState(this.playState, this.playPath);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/lib/const/record_state.dart:
--------------------------------------------------------------------------------
1 | class RecordState {
2 | ///发送到原生端的方法名
3 | static String init = "init";
4 | static String start = "start";
5 | static String startByWavPath = "startByWavPath";
6 | static String stop = "stop";
7 | static String play = "play";
8 | static String playByPath = "playByPath";
9 |
10 | ///原生端的回调方法名
11 | static String onInit = "onInit";
12 | static String onStart = "onStart";
13 | static String onStop = "onStop";
14 | static String onPlay = "onPlay";
15 | static String onAmplitude = "onAmplitude";
16 | static String onPlayState = "onPlayState";
17 | }
18 |
--------------------------------------------------------------------------------
/lib/const/response.dart:
--------------------------------------------------------------------------------
1 | class RecordResponse {
2 | bool? success;
3 | String? path;
4 | String? msg;
5 | String? key;
6 | double? audioTimeLength;
7 |
8 | RecordResponse(
9 | {this.success, this.path, this.msg, this.key, this.audioTimeLength});
10 | // RecordResponse({this.success, this.path,this.msg,this.key});
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/lib/flutter_plugin_record.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_plugin_record/const/play_state.dart';
5 | import 'package:flutter_plugin_record/const/response.dart';
6 | import 'package:uuid/uuid.dart';
7 |
8 | class FlutterPluginRecord {
9 | final MethodChannel _channel = const MethodChannel('flutter_plugin_record');
10 |
11 | static final _uuid = new Uuid();
12 | String id = '';
13 | static final alis = new Map();
14 |
15 | FlutterPluginRecord() {
16 | id = _uuid.v4();
17 | alis[id] = this;
18 | // print("--------FlutterPluginRecord init");
19 | }
20 |
21 | ///Flutter 调用原生初始化
22 | Future _invokeMethod(String method,
23 | [Map arguments = const {}]) {
24 | Map withId = Map.of(arguments);
25 | withId['id'] = id;
26 | _channel.setMethodCallHandler(_handler);
27 | return _channel.invokeMethod(method, withId);
28 | }
29 |
30 | ///初始化init的回调
31 | StreamController _responseInitController =
32 | new StreamController.broadcast();
33 |
34 | Stream get responseFromInit => _responseInitController.stream;
35 |
36 | ///开始录制 停止录制的回调监听
37 | StreamController _responseController =
38 | new StreamController.broadcast();
39 |
40 | Stream get response => _responseController.stream;
41 |
42 | ///音量高低的回调
43 | StreamController _responseAmplitudeController =
44 | new StreamController.broadcast();
45 |
46 | Stream get responseFromAmplitude =>
47 | _responseAmplitudeController.stream;
48 |
49 | ///播放状态监听
50 | StreamController _responsePlayStateController =
51 | new StreamController.broadcast();
52 |
53 | Stream get responsePlayStateController =>
54 | _responsePlayStateController.stream;
55 |
56 | ///原生回调
57 | static Future _handler(MethodCall methodCall) async{
58 | // print("--------FlutterPluginRecord " + methodCall.method);
59 |
60 | String id = (methodCall.arguments as Map)['id'];
61 | FlutterPluginRecord recordPlugin = alis[id] ?? FlutterPluginRecord();
62 | switch (methodCall.method) {
63 | case "onInit":
64 | bool flag = false;
65 | if ("success" == methodCall.arguments["result"]) {
66 | flag = true;
67 | }
68 | recordPlugin._responseInitController.add(flag);
69 | break;
70 | case "onStart":
71 | if ("success" == methodCall.arguments["result"]) {
72 | RecordResponse res = new RecordResponse(
73 | success: true,
74 | path: "",
75 | msg: "onStart",
76 | key: methodCall.arguments["key"].toString(),
77 | );
78 | recordPlugin._responseController.add(res);
79 | }
80 |
81 | break;
82 | case "onStop":
83 | if ("success" == methodCall.arguments["result"]) {
84 | RecordResponse res = new RecordResponse(
85 | success: true,
86 | path: methodCall.arguments["voicePath"].toString(),
87 | audioTimeLength:
88 | double.parse(methodCall.arguments["audioTimeLength"]),
89 | msg: "onStop",
90 | key: methodCall.arguments["key"].toString(),
91 | );
92 | recordPlugin._responseController.add(res);
93 | }
94 |
95 | break;
96 | case "onPlay":
97 | RecordResponse res = new RecordResponse(
98 | success: true,
99 | path: "",
100 | msg: "开始播放",
101 | key: methodCall.arguments["key"].toString(),
102 | );
103 | recordPlugin._responseController.add(res);
104 | break;
105 | case "onAmplitude":
106 | if ("success" == methodCall.arguments["result"]) {
107 | RecordResponse res = new RecordResponse(
108 | success: true,
109 | path: "",
110 | msg: methodCall.arguments["amplitude"].toString(),
111 | key: methodCall.arguments["key"].toString(),
112 | );
113 | recordPlugin._responseAmplitudeController.add(res);
114 | }
115 | break;
116 | case "onPlayState":
117 | var playState = methodCall.arguments["playState"];
118 | var playPath = methodCall.arguments["playPath"];
119 | PlayState res = new PlayState(playState, playPath);
120 | recordPlugin._responsePlayStateController.add(res);
121 | break;
122 | case "pausePlay":
123 | //暂停或继续播放
124 | var isPlaying = methodCall.arguments["isPlaying"];
125 | PlayState res = new PlayState(isPlaying, "");
126 | recordPlugin._responsePlayStateController.add(res);
127 | break;
128 | default:
129 | print("default");
130 | break;
131 | }
132 | return null;
133 | }
134 |
135 | //初始化
136 | Future init() async {
137 | return await _invokeMethod('init', {
138 | "init": "init",
139 | });
140 | }
141 |
142 | //初始化
143 | Future initRecordMp3() async {
144 | return await _invokeMethod('initRecordMp3', {
145 | "initRecordMp3": "initRecordMp3",
146 | });
147 | }
148 |
149 | Future start() async {
150 | return await _invokeMethod('start', {
151 | "start": "start",
152 | });
153 | }
154 |
155 | Future startByWavPath(String wavPath) async {
156 | return await _invokeMethod('startByWavPath', {
157 | "wavPath": wavPath,
158 | });
159 | }
160 |
161 | Future stop() async {
162 | return await _invokeMethod('stop', {
163 | "stop": "stop",
164 | });
165 | }
166 |
167 | Future play() async {
168 | return await _invokeMethod('play', {
169 | "play": "play",
170 | });
171 | }
172 |
173 | // Future playByPath(String path) async {
174 | // return await _invokeMethod('playByPath', {
175 | // "play": "play",
176 | // "path": path,
177 | // });
178 | // }
179 |
180 | ///
181 | /// 参数 path 播放音频的地址
182 | ///
183 | ///path 为 url类型对应的在线播放地址 https://linjuli-app-audio.oss-cn-hangzhou.aliyuncs.com/audio/50c39c768b534ce1ba25d837ed153824.wav
184 | ///path 对应本地文件路径对应的是本地文件播放肚子 /sdcard/flutterdemo/wiw.wav
185 | /// 参数 type
186 | /// 当path 为url type为 url
187 | /// 当path 为本地地址 type为 file
188 | ///
189 | Future playByPath(String path, String type) async {
190 | return await _invokeMethod('playByPath', {
191 | "play": "play",
192 | "path": path,
193 | "type": type,
194 | });
195 | }
196 |
197 | ///暂停播放
198 | Future pausePlay() async {
199 | return await _invokeMethod('pause', {
200 | "pause": "pause",
201 | });
202 | }
203 |
204 | /// 提供停止播放的功能
205 | Future stopPlay() async {
206 | return await _invokeMethod('stopPlay', {});
207 | }
208 |
209 | dispose() {
210 | // stopPlay();
211 | _responseInitController.close();
212 | _responseController.close();
213 | _responseAmplitudeController.close();
214 | _responsePlayStateController.close();
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/lib/index.dart:
--------------------------------------------------------------------------------
1 | export 'const/record_state.dart';
2 | export 'flutter_plugin_record.dart';
3 | export 'utils/common_toast.dart';
4 | export 'widgets/custom_overlay.dart';
5 | export 'widgets/voice_widget.dart';
6 |
--------------------------------------------------------------------------------
/lib/utils/common_toast.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_plugin_record/widgets/custom_overlay.dart';
5 |
6 | class CommonToast {
7 | static showView({
8 | BuildContext? context,
9 | String? msg,
10 | TextStyle? style,
11 | Widget? icon,
12 | Duration duration = const Duration(seconds: 1),
13 | int count = 3,
14 | Function? onTap,
15 | }) {
16 | OverlayEntry? overlayEntry;
17 | int _count = 0;
18 |
19 | void removeOverlay() {
20 | overlayEntry?.remove();
21 | overlayEntry = null;
22 | }
23 |
24 | if (overlayEntry == null) {
25 | overlayEntry = new OverlayEntry(builder: (content) {
26 | return Container(
27 | child: GestureDetector(
28 | onTap: () {
29 | if (onTap != null) {
30 | removeOverlay();
31 | onTap();
32 | }
33 | },
34 | child: CustomOverlay(
35 | icon: Column(
36 | children: [
37 | Padding(
38 | child: icon,
39 | padding: const EdgeInsets.only(
40 | bottom: 10.0,
41 | ),
42 | ),
43 | Container(
44 | // padding: EdgeInsets.only(right: 20, left: 20, top: 0),
45 | child: Text(
46 | msg ?? '',
47 | style: style ??
48 | TextStyle(
49 | fontStyle: FontStyle.normal,
50 | color: Colors.white,
51 | fontSize: 16,
52 | ),
53 | ),
54 | )
55 | ],
56 | ),
57 | ),
58 | ),
59 | );
60 | });
61 | Overlay.of(context!)!.insert(overlayEntry!);
62 | if (onTap != null) return;
63 | Timer.periodic(duration, (timer) {
64 | _count++;
65 | if (_count == count) {
66 | _count = 0;
67 | timer.cancel();
68 | removeOverlay();
69 | }
70 | });
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/widgets/custom_overlay.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomOverlay extends StatelessWidget {
4 | final Widget? icon;
5 | final BoxDecoration decoration;
6 | final double width;
7 | final double height;
8 | const CustomOverlay({
9 | Key? key,
10 | this.icon,
11 | this.decoration = const BoxDecoration(
12 | color: Color(0xff77797A),
13 | borderRadius: BorderRadius.all(Radius.circular(20.0)),
14 | ),
15 | this.width = 160,
16 | this.height = 160,
17 | }) : super(key: key);
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return Positioned(
22 | top: MediaQuery.of(context).size.height * 0.5 - width / 2,
23 | left: MediaQuery.of(context).size.width * 0.5 - height / 2,
24 | child: Material(
25 | type: MaterialType.transparency,
26 | child: Center(
27 | child: Opacity(
28 | opacity: 0.8,
29 | child: Container(
30 | width: width,
31 | height: height,
32 | decoration: decoration,
33 | child: icon,
34 | ),
35 | ),
36 | ),
37 | ),
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/widgets/voice_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_plugin_record/flutter_plugin_record.dart';
5 | import 'package:flutter_plugin_record/utils/common_toast.dart';
6 |
7 | import 'custom_overlay.dart';
8 |
9 | typedef startRecord = Future Function();
10 | typedef stopRecord = Future Function();
11 |
12 | class VoiceWidget extends StatefulWidget {
13 | final Function? startRecord;
14 | final Function? stopRecord;
15 | final double? height;
16 | final EdgeInsets? margin;
17 | final Decoration? decoration;
18 |
19 | /// startRecord 开始录制回调 stopRecord回调
20 | const VoiceWidget(
21 | {Key? key,
22 | this.startRecord,
23 | this.stopRecord,
24 | this.height,
25 | this.decoration,
26 | this.margin})
27 | : super(key: key);
28 |
29 | @override
30 | _VoiceWidgetState createState() => _VoiceWidgetState();
31 | }
32 |
33 | class _VoiceWidgetState extends State {
34 | // 倒计时总时长
35 | int _countTotal = 12;
36 | double starty = 0.0;
37 | double offset = 0.0;
38 | bool isUp = false;
39 | String textShow = "按住说话";
40 | String toastShow = "手指上滑,取消发送";
41 | String voiceIco = "images/voice_volume_1.png";
42 |
43 | ///默认隐藏状态
44 | bool voiceState = true;
45 | FlutterPluginRecord? recordPlugin;
46 | Timer? _timer;
47 | int _count = 0;
48 | OverlayEntry? overlayEntry;
49 |
50 | @override
51 | void initState() {
52 | super.initState();
53 | recordPlugin = new FlutterPluginRecord();
54 |
55 | _init();
56 |
57 | ///初始化方法的监听
58 | recordPlugin?.responseFromInit.listen((data) {
59 | if (data) {
60 | print("初始化成功");
61 | } else {
62 | print("初始化失败");
63 | }
64 | });
65 |
66 | /// 开始录制或结束录制的监听
67 | recordPlugin?.response.listen((data) {
68 | if (data.msg == "onStop") {
69 | ///结束录制时会返回录制文件的地址方便上传服务器
70 | print("onStop " + data.path!);
71 | if (widget.stopRecord != null)
72 | widget.stopRecord!(data.path, data.audioTimeLength);
73 | } else if (data.msg == "onStart") {
74 | print("onStart --");
75 | if (widget.startRecord != null) widget.startRecord!();
76 | }
77 | });
78 |
79 | ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式
80 | recordPlugin!.responseFromAmplitude.listen((data) {
81 | var voiceData = double.parse(data.msg ?? '');
82 | setState(() {
83 | if (voiceData > 0 && voiceData < 0.1) {
84 | voiceIco = "images/voice_volume_2.png";
85 | } else if (voiceData > 0.2 && voiceData < 0.3) {
86 | voiceIco = "images/voice_volume_3.png";
87 | } else if (voiceData > 0.3 && voiceData < 0.4) {
88 | voiceIco = "images/voice_volume_4.png";
89 | } else if (voiceData > 0.4 && voiceData < 0.5) {
90 | voiceIco = "images/voice_volume_5.png";
91 | } else if (voiceData > 0.5 && voiceData < 0.6) {
92 | voiceIco = "images/voice_volume_6.png";
93 | } else if (voiceData > 0.6 && voiceData < 0.7) {
94 | voiceIco = "images/voice_volume_7.png";
95 | } else if (voiceData > 0.7 && voiceData < 1) {
96 | voiceIco = "images/voice_volume_7.png";
97 | } else {
98 | voiceIco = "images/voice_volume_1.png";
99 | }
100 | if (overlayEntry != null) {
101 | overlayEntry!.markNeedsBuild();
102 | }
103 | });
104 |
105 | print("振幅大小 " + voiceData.toString() + " " + voiceIco);
106 | });
107 | }
108 |
109 | ///显示录音悬浮布局
110 | buildOverLayView(BuildContext context) {
111 | if (overlayEntry == null) {
112 | overlayEntry = new OverlayEntry(builder: (content) {
113 | return CustomOverlay(
114 | icon: Column(
115 | children: [
116 | Container(
117 | margin: const EdgeInsets.only(top: 10),
118 | child: _countTotal - _count < 11
119 | ? Center(
120 | child: Padding(
121 | padding: const EdgeInsets.only(bottom: 15.0),
122 | child: Text(
123 | (_countTotal - _count).toString(),
124 | style: TextStyle(
125 | fontSize: 70.0,
126 | color: Colors.white,
127 | ),
128 | ),
129 | ),
130 | )
131 | : new Image.asset(
132 | voiceIco,
133 | width: 100,
134 | height: 100,
135 | package: 'flutter_plugin_record',
136 | ),
137 | ),
138 | Container(
139 | // padding: const EdgeInsets.only(right: 20, left: 20, top: 0),
140 | child: Text(
141 | toastShow,
142 | style: TextStyle(
143 | fontStyle: FontStyle.normal,
144 | color: Colors.white,
145 | fontSize: 14,
146 | ),
147 | ),
148 | )
149 | ],
150 | ),
151 | );
152 | });
153 | Overlay.of(context)!.insert(overlayEntry!);
154 | }
155 | }
156 |
157 | showVoiceView() {
158 | setState(() {
159 | textShow = "松开结束";
160 | voiceState = false;
161 | });
162 |
163 | ///显示录音悬浮布局
164 | buildOverLayView(context);
165 |
166 | start();
167 | }
168 |
169 | hideVoiceView() {
170 | if (_timer!.isActive) {
171 | if (_count < 1) {
172 | CommonToast.showView(
173 | context: context,
174 | msg: '说话时间太短',
175 | icon: Text(
176 | '!',
177 | style: TextStyle(fontSize: 80, color: Colors.white),
178 | ));
179 | isUp = true;
180 | }
181 | _timer?.cancel();
182 | _count = 0;
183 | }
184 |
185 | setState(() {
186 | textShow = "按住说话";
187 | voiceState = true;
188 | });
189 |
190 | stop();
191 | if (overlayEntry != null) {
192 | overlayEntry?.remove();
193 | overlayEntry = null;
194 | }
195 |
196 | if (isUp) {
197 | print("取消发送");
198 | } else {
199 | print("进行发送");
200 | }
201 | }
202 |
203 | moveVoiceView() {
204 | // print(offset - start);
205 | setState(() {
206 | isUp = starty - offset > 100 ? true : false;
207 | if (isUp) {
208 | textShow = "松开手指,取消发送";
209 | toastShow = textShow;
210 | } else {
211 | textShow = "松开结束";
212 | toastShow = "手指上滑,取消发送";
213 | }
214 | });
215 | }
216 |
217 | ///初始化语音录制的方法
218 | void _init() async {
219 | recordPlugin?.init();
220 | }
221 |
222 | ///开始语音录制的方法
223 | void start() async {
224 | recordPlugin?.start();
225 | }
226 |
227 | ///停止语音录制的方法
228 | void stop() {
229 | recordPlugin?.stop();
230 | }
231 |
232 | @override
233 | Widget build(BuildContext context) {
234 | return Container(
235 | child: GestureDetector(
236 | onLongPressStart: (details) {
237 | starty = details.globalPosition.dy;
238 | _timer = Timer.periodic(Duration(milliseconds: 1000), (t) {
239 | _count++;
240 | print('_count is 👉 $_count');
241 | if (_count == _countTotal) {
242 | hideVoiceView();
243 | }
244 | });
245 | showVoiceView();
246 | },
247 | onLongPressEnd: (details) {
248 | hideVoiceView();
249 | },
250 | onLongPressMoveUpdate: (details) {
251 | offset = details.globalPosition.dy;
252 | moveVoiceView();
253 | },
254 | child: Container(
255 | height: widget.height ?? 60,
256 | // color: Colors.blue,
257 | decoration: widget.decoration ??
258 | BoxDecoration(
259 | borderRadius: new BorderRadius.circular(6.0),
260 | border: Border.all(width: 1.0, color: Colors.grey.shade200),
261 | ),
262 | margin: widget.margin ?? EdgeInsets.fromLTRB(50, 0, 50, 20),
263 | child: Center(
264 | child: Text(
265 | textShow,
266 | ),
267 | ),
268 | ),
269 | ),
270 | );
271 | }
272 |
273 | @override
274 | void dispose() {
275 | recordPlugin?.dispose();
276 | _timer?.cancel();
277 | super.dispose();
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.5.0"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.2.0"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | crypto:
47 | dependency: transitive
48 | description:
49 | name: crypto
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "3.0.0"
53 | fake_async:
54 | dependency: transitive
55 | description:
56 | name: fake_async
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "1.2.0"
60 | flutter:
61 | dependency: "direct main"
62 | description: flutter
63 | source: sdk
64 | version: "0.0.0"
65 | flutter_test:
66 | dependency: "direct dev"
67 | description: flutter
68 | source: sdk
69 | version: "0.0.0"
70 | matcher:
71 | dependency: transitive
72 | description:
73 | name: matcher
74 | url: "https://pub.flutter-io.cn"
75 | source: hosted
76 | version: "0.12.10"
77 | meta:
78 | dependency: transitive
79 | description:
80 | name: meta
81 | url: "https://pub.flutter-io.cn"
82 | source: hosted
83 | version: "1.3.0"
84 | path:
85 | dependency: transitive
86 | description:
87 | name: path
88 | url: "https://pub.flutter-io.cn"
89 | source: hosted
90 | version: "1.8.0"
91 | sky_engine:
92 | dependency: transitive
93 | description: flutter
94 | source: sdk
95 | version: "0.0.99"
96 | source_span:
97 | dependency: transitive
98 | description:
99 | name: source_span
100 | url: "https://pub.flutter-io.cn"
101 | source: hosted
102 | version: "1.8.0"
103 | stack_trace:
104 | dependency: transitive
105 | description:
106 | name: stack_trace
107 | url: "https://pub.flutter-io.cn"
108 | source: hosted
109 | version: "1.10.0"
110 | stream_channel:
111 | dependency: transitive
112 | description:
113 | name: stream_channel
114 | url: "https://pub.flutter-io.cn"
115 | source: hosted
116 | version: "2.1.0"
117 | string_scanner:
118 | dependency: transitive
119 | description:
120 | name: string_scanner
121 | url: "https://pub.flutter-io.cn"
122 | source: hosted
123 | version: "1.1.0"
124 | term_glyph:
125 | dependency: transitive
126 | description:
127 | name: term_glyph
128 | url: "https://pub.flutter-io.cn"
129 | source: hosted
130 | version: "1.2.0"
131 | test_api:
132 | dependency: transitive
133 | description:
134 | name: test_api
135 | url: "https://pub.flutter-io.cn"
136 | source: hosted
137 | version: "0.2.19"
138 | typed_data:
139 | dependency: transitive
140 | description:
141 | name: typed_data
142 | url: "https://pub.flutter-io.cn"
143 | source: hosted
144 | version: "1.3.0"
145 | uuid:
146 | dependency: "direct main"
147 | description:
148 | name: uuid
149 | url: "https://pub.flutter-io.cn"
150 | source: hosted
151 | version: "3.0.2"
152 | vector_math:
153 | dependency: transitive
154 | description:
155 | name: vector_math
156 | url: "https://pub.flutter-io.cn"
157 | source: hosted
158 | version: "2.1.0"
159 | sdks:
160 | dart: ">=2.12.0-29.10.beta <3.0.0"
161 | flutter: ">=1.12.0"
162 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 |
2 | name: flutter_plugin_record
3 | description: The flutter voice recording plug-in,provides the recording animation and the recording successfully returns to the recording file path
4 | version: 1.0.1
5 | #author: wilson
6 | homepage: https://github.com/yxwandroid/flutter_plugin_record
7 | #publish_to: none
8 |
9 |
10 | environment:
11 | sdk: ">=2.12.0-29.10.beta <3.0.0"
12 | flutter: ">=1.12.0"
13 |
14 |
15 | dependencies:
16 | flutter:
17 | sdk: flutter
18 | uuid: ^3.0.0
19 |
20 | dev_dependencies:
21 | flutter_test:
22 | sdk: flutter
23 |
24 | # For information on the generic Dart part of this file, see the
25 | # following page: https://dart.dev/tools/pub/pubspec
26 |
27 | # The following section is specific to Flutter.
28 | flutter:
29 | # This section identifies this Flutter project as a plugin project.
30 | # The androidPackage and pluginClass identifiers should not ordinarily
31 | # be modified. They are used by the tooling to maintain consistency when
32 | # adding or updating assets for this project.
33 | plugin:
34 | # androidPackage: record.wilson.flutter.com.flutter_plugin_record
35 | # pluginClass: FlutterPluginRecordPlugin
36 | platforms:
37 | android:
38 | package: record.wilson.flutter.com.flutter_plugin_record
39 | pluginClass: FlutterPluginRecordPlugin
40 | ios:
41 | pluginClass: FlutterPluginRecordPlugin
42 |
43 | uses-material-design: true
44 | assets:
45 | - images/voice_volume_1.png
46 | - images/voice_volume_2.png
47 | - images/voice_volume_3.png
48 | - images/voice_volume_4.png
49 | - images/voice_volume_5.png
50 | - images/voice_volume_6.png
51 | - images/voice_volume_7.png
52 |
53 | # To add assets to your plugin package, add an assets section, like this:
54 | # assets:
55 | # - images/a_dot_burr.jpeg
56 | # - images/a_dot_ham.jpeg
57 | #
58 | # For details regarding assets in packages, see
59 | # https://flutter.dev/assets-and-images/#from-packages
60 | #
61 | # An image asset can refer to one or more resolution-specific "variants", see
62 | # https://flutter.dev/assets-and-images/#resolution-aware.
63 |
64 | # To add custom fonts to your plugin package, add a fonts section here,
65 | # in this "flutter" section. Each entry in this list should have a
66 | # "family" key with the font family name, and a "fonts" key with a
67 | # list giving the asset and other descriptors for the font. For
68 | # example:
69 | # fonts:
70 | # - family: Schyler
71 | # fonts:
72 | # - asset: fonts/Schyler-Regular.ttf
73 | # - asset: fonts/Schyler-Italic.ttf
74 | # style: italic
75 | # - family: Trajan Pro
76 | # fonts:
77 | # - asset: fonts/TrajanPro.ttf
78 | # - asset: fonts/TrajanPro_Bold.ttf
79 | # weight: 700
80 | #
81 | # For details regarding fonts in packages, see
82 | # https://flutter.dev/custom-fonts/#from-packages
83 |
--------------------------------------------------------------------------------
/test/flutter_plugin_record_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | const MethodChannel channel = MethodChannel('flutter_plugin_record');
6 |
7 | setUp(() {
8 | channel.setMockMethodCallHandler((MethodCall methodCall) async {
9 | return '42';
10 | });
11 | });
12 |
13 | tearDown(() {
14 | channel.setMockMethodCallHandler(null);
15 | });
16 |
17 | // test('getPlatformVersion', () async {
18 | // expect(await FlutterPluginRecord.platformVersion, '42');
19 | // });
20 | }
21 |
--------------------------------------------------------------------------------
/飞云之下.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yxwandroid/flutter_plugin_record/8e6f3cf9ebad75ef3bc9b45bb16ef31c49596195/飞云之下.wav
--------------------------------------------------------------------------------