Skip to content

Commit db41f46

Browse files
authored
Merge pull request Sub6Resources#622 from tneotia/bugfix/audio-video-disposing
Fix audio/video not disposing when going to a different screen
2 parents 2f3acc0 + bf7be3d commit db41f46

File tree

9 files changed

+180
-68
lines changed

9 files changed

+180
-68
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets.
3939

4040
- [Parameters Table](#parameters)
4141

42+
- [Methods](#methods)
43+
4244
- [Getters](#getters)
4345

4446
- [Data](#data)
@@ -187,12 +189,26 @@ Once the above issue is resolved, the aforementioned compromises will go away. C
187189
| `customImageRender` | A powerful API that allows you to fully customize how images are loaded. |
188190
| `selectionControls` | A custom text selection controls that allow you to override default toolbar and build toolbar with custom text selection options. See an [example](https://github.com/justinmc/flutter-text-selection-menu-examples/blob/master/lib/custom_menu_page.dart). |
189191

192+
### Methods:
193+
194+
| Methods | Description |
195+
|--------------|-----------------|
196+
| `disposeAll()` | Disposes all `ChewieController`s, `ChewieAudioController`s, and `VideoPlayerController`s being used by every `Html` widget. (Note: `Html` widgets automatically dispose their controllers, this method is only provided in case you need other behavior) |
197+
190198
### Getters:
191199

192200
1. `Html.tags`. This provides a list of all the tags the package renders. The main use case is to assist in excluding elements using `tagsList`. See an [example](#example-usage---tagslist---excluding-tags) below.
193201

194202
2. `SelectableHtml.tags`. This provides a list of all the tags that can be rendered in selectable mode.
195203

204+
3. `Html.chewieAudioControllers`. This provides a list of all `ChewieAudioController`s being used by `Html` widgets.
205+
206+
4. `Html.chewieControllers`. This provides a list of all `ChewieController`s being used by `Html` widgets.
207+
208+
5. `Html.videoPlayerControllers`. This provides a list of all `VideoPlayerController`s being used for video widgets by `Html` widgets.
209+
210+
6. `Html.audioPlayerControllers`. This provides a list of all `VideoPlayerController`s being used for audio widgets by `Html` widgets.
211+
196212
### Data:
197213

198214
The HTML data passed to the `Html` widget as a `String`. This is required and cannot be null when using `Html`.

lib/flutter_html.dart

Lines changed: 115 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
library flutter_html;
22

3+
import 'package:chewie/chewie.dart';
4+
import 'package:chewie_audio/chewie_audio.dart';
35
import 'package:flutter/material.dart';
46
import 'package:flutter_html/custom_render.dart';
57
import 'package:flutter/rendering.dart';
68
import 'package:flutter_html/html_parser.dart';
79
import 'package:flutter_html/image_render.dart';
810
import 'package:flutter_html/src/html_elements.dart';
11+
import 'package:flutter_html/src/utils.dart';
912
import 'package:flutter_html/style.dart';
1013
import 'package:html/dom.dart' as dom;
1114
import 'package:flutter_html/src/navigation_delegate.dart';
15+
import 'package:video_player/video_player.dart';
1216

1317
//export render context api
1418
export 'package:flutter_html/html_parser.dart';
@@ -29,7 +33,7 @@ export 'package:flutter_html/src/navigation_delegate.dart';
2933
//export style api
3034
export 'package:flutter_html/style.dart';
3135

32-
class Html extends StatelessWidget {
36+
class Html extends StatefulWidget {
3337
/// The `Html` widget takes HTML as input and displays a RichText
3438
/// tree of the parsed HTML content.
3539
///
@@ -149,41 +153,126 @@ class Html extends StatelessWidget {
149153
/// to use NavigationDelegate.
150154
final NavigationDelegate? navigationDelegateForIframe;
151155

152-
static List<String> get tags => new List<String>.from(STYLED_ELEMENTS)
153-
..addAll(INTERACTABLE_ELEMENTS)
154-
..addAll(REPLACED_ELEMENTS)
155-
..addAll(LAYOUT_ELEMENTS)
156-
..addAll(TABLE_CELL_ELEMENTS)
157-
..addAll(TABLE_DEFINITION_ELEMENTS);
156+
/// Get the list of supported tags for the [Html] widget
157+
static List<String> get tags =>
158+
new List<String>.from(STYLED_ELEMENTS)
159+
..addAll(INTERACTABLE_ELEMENTS)..addAll(REPLACED_ELEMENTS)..addAll(
160+
LAYOUT_ELEMENTS)..addAll(TABLE_CELL_ELEMENTS)..addAll(
161+
TABLE_DEFINITION_ELEMENTS);
162+
163+
/// Protected member to track controllers used in all [Html] widgets. Please
164+
/// refrain from using this member, and rather use the [chewieAudioControllers],
165+
/// [chewieControllers], [videoPlayerControllers], and [audioPlayerControllers]
166+
/// getters to access the controllers in your own code.
167+
@protected
168+
static final InternalControllers controllers = InternalControllers();
169+
170+
/// Internal member to track controllers used in the specific [Html] widget.
171+
/// This is only used so controllers can be automatically disposed when the
172+
/// widget disposes.
173+
final InternalControllers _controllers = InternalControllers();
174+
175+
/// Getter for all [ChewieAudioController]s initialized by [Html] widgets.
176+
static List<ChewieAudioController> get chewieAudioControllers => controllers.chewieAudioControllers.values.toList();
177+
/// Getter for all [ChewieController]s initialized by [Html] widgets.
178+
static List<ChewieController> get chewieControllers => controllers.chewieControllers.values.toList();
179+
/// Getter for all [VideoPlayerController]s for video widgets initialized by [Html] widgets.
180+
static List<VideoPlayerController> get videoPlayerControllers => controllers.videoPlayerControllers.values.toList();
181+
/// Getter for all [VideoPlayerController]s for audio widgets initialized by [Html] widgets.
182+
static List<VideoPlayerController> get audioPlayerControllers => controllers.audioPlayerControllers.values.toList();
183+
184+
/// Convenience method to dispose all controllers used by all [Html] widgets
185+
/// at this time. This is not necessary to be called, as each [Html] widget
186+
/// will automatically handle disposing.
187+
static void disposeAll() {
188+
controllers.chewieAudioControllers.values.forEach((element) {
189+
element.dispose();
190+
});
191+
controllers.chewieControllers.values.forEach((ChewieController element) {
192+
element.dispose();
193+
});
194+
controllers.videoPlayerControllers.values.forEach((element) {
195+
element.dispose();
196+
});
197+
controllers.audioPlayerControllers.values.forEach((element) {
198+
element.dispose();
199+
});
200+
}
201+
202+
/// Internal method to add controllers to the global list and widget-specific
203+
/// list. This should not be used in your app code.
204+
void addController(int hashCode, dynamic controller, {bool isAudioController = false}) {
205+
if (controller is ChewieAudioController) {
206+
controllers.chewieAudioControllers[hashCode] = controller;
207+
_controllers.chewieAudioControllers[hashCode] = controller;
208+
} else if (controller is ChewieController) {
209+
controllers.chewieControllers[hashCode] = controller;
210+
_controllers.chewieControllers[hashCode] = controller;
211+
} else if (controller is VideoPlayerController && !isAudioController) {
212+
controllers.videoPlayerControllers[hashCode] = controller;
213+
_controllers.videoPlayerControllers[hashCode] = controller;
214+
} else if (controller is VideoPlayerController) {
215+
controllers.audioPlayerControllers[hashCode] = controller;
216+
_controllers.audioPlayerControllers[hashCode] = controller;
217+
}
218+
}
158219

159220
@override
160-
Widget build(BuildContext context) {
161-
final dom.Document doc =
162-
data != null ? HtmlParser.parseHTML(data!) : document!;
163-
final double? width = shrinkWrap ? null : MediaQuery.of(context).size.width;
221+
State<StatefulWidget> createState() => _HtmlState();
222+
}
164223

224+
class _HtmlState extends State<Html> {
225+
late final dom.Document doc;
226+
227+
@override
228+
void initState() {
229+
super.initState();
230+
doc =
231+
widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.document!;
232+
}
233+
234+
@override
235+
void dispose() {
236+
widget._controllers.chewieAudioControllers.values.forEach((element) {
237+
element.dispose();
238+
});
239+
widget._controllers.chewieControllers.values.forEach((ChewieController element) {
240+
element.dispose();
241+
});
242+
widget._controllers.videoPlayerControllers.values.forEach((element) {
243+
element.dispose();
244+
});
245+
widget._controllers.audioPlayerControllers.values.forEach((element) {
246+
element.dispose();
247+
});
248+
super.dispose();
249+
}
250+
251+
@override
252+
Widget build(BuildContext context) {
165253
return Container(
166-
width: width,
254+
width: widget.shrinkWrap ? null : MediaQuery.of(context).size.width,
167255
child: HtmlParser(
168-
key: _anchorKey,
256+
key: widget._anchorKey,
169257
htmlData: doc,
170-
onLinkTap: onLinkTap,
171-
onAnchorTap: onAnchorTap,
172-
onImageTap: onImageTap,
173-
onCssParseError: onCssParseError,
174-
onImageError: onImageError,
175-
onMathError: onMathError,
176-
shrinkWrap: shrinkWrap,
258+
onLinkTap: widget.onLinkTap,
259+
onAnchorTap: widget.onAnchorTap,
260+
onImageTap: widget.onImageTap,
261+
onCssParseError: widget.onCssParseError,
262+
onImageError: widget.onImageError,
263+
onMathError: widget.onMathError,
264+
shrinkWrap: widget.shrinkWrap,
177265
selectable: false,
178-
style: style,
266+
style: widget.style,
179267
customRenders: {}
180-
..addAll(customRenders)
268+
..addAll(widget.customRenders)
181269
..addAll(defaultRenders),
182270
imageRenders: {}
183-
..addAll(customImageRenders)
271+
..addAll(widget.customImageRenders)
184272
..addAll(defaultImageRenders),
185-
tagsList: tagsList.isEmpty ? Html.tags : tagsList,
186-
navigationDelegateForIframe: navigationDelegateForIframe,
273+
tagsList: widget.tagsList.isEmpty ? Html.tags : widget.tagsList,
274+
navigationDelegateForIframe: widget.navigationDelegateForIframe,
275+
root: widget,
187276
),
188277
);
189278
}
@@ -297,6 +386,7 @@ class SelectableHtml extends StatelessWidget {
297386
/// fallback to the default rendering.
298387
final Map<CustomRenderMatcher, SelectableCustomRender> customRenders;
299388

389+
/// Get the list of supported tags for the [SelectableHtml] widget
300390
static List<String> get tags => new List<String>.from(SELECTABLE_ELEMENTS);
301391

302392
@override

lib/html_parser.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,10 @@ import 'dart:math';
44
import 'package:csslib/parser.dart' as cssparser;
55
import 'package:csslib/visitor.dart' as css;
66
import 'package:flutter/material.dart';
7-
import 'package:flutter/rendering.dart';
87
import 'package:flutter_html/flutter_html.dart';
9-
import 'package:flutter_html/image_render.dart';
10-
import 'package:flutter_html/src/anchor.dart';
118
import 'package:flutter_html/src/css_parser.dart';
129
import 'package:flutter_html/src/html_elements.dart';
13-
import 'package:flutter_html/src/layout_element.dart';
14-
import 'package:flutter_html/src/navigation_delegate.dart';
1510
import 'package:flutter_html/src/utils.dart';
16-
import 'package:flutter_html/style.dart';
1711
import 'package:html/dom.dart' as dom;
1812
import 'package:html/parser.dart' as htmlparser;
1913
import 'package:numerus/numerus.dart';
@@ -52,6 +46,7 @@ class HtmlParser extends StatelessWidget {
5246
final List<String> tagsList;
5347
final NavigationDelegate? navigationDelegateForIframe;
5448
final OnTap? internalOnAnchorTap;
49+
final Html? root;
5550
final TextSelectionControls? selectionControls;
5651
final ScrollPhysics? scrollPhysics;
5752

@@ -73,6 +68,7 @@ class HtmlParser extends StatelessWidget {
7368
required this.imageRenders,
7469
required this.tagsList,
7570
required this.navigationDelegateForIframe,
71+
this.root,
7672
this.selectionControls,
7773
this.scrollPhysics,
7874
}) : this.internalOnAnchorTap = onAnchorTap != null

lib/src/anchor.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'package:flutter/foundation.dart';
21
import 'package:flutter/widgets.dart';
32
import 'package:flutter_html/src/styled_element.dart';
43

lib/src/css_parser.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import 'dart:ui';
33
import 'package:collection/collection.dart';
44
import 'package:csslib/visitor.dart' as css;
55
import 'package:csslib/parser.dart' as cssparser;
6-
import 'package:flutter/cupertino.dart';
76
import 'package:flutter/material.dart';
87
import 'package:flutter_html/flutter_html.dart';
98
import 'package:flutter_html/src/utils.dart';
10-
import 'package:flutter_html/style.dart';
119

1210
Style declarationsToStyle(Map<String, List<css.Expression>> declarations) {
1311
Style style = new Style();

lib/src/replaced_element.dart

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@ import 'dart:math';
33
import 'package:chewie/chewie.dart';
44
import 'package:chewie_audio/chewie_audio.dart';
55
import 'package:flutter/material.dart';
6-
import 'package:flutter/widgets.dart';
7-
import 'package:flutter_html/html_parser.dart';
8-
import 'package:flutter_html/src/anchor.dart';
9-
import 'package:flutter_html/src/html_elements.dart';
10-
import 'package:flutter_html/src/navigation_delegate.dart';
6+
import 'package:flutter_html/flutter_html.dart';
117
import 'package:flutter_html/src/utils.dart';
128
import 'package:flutter_html/src/widgets/iframe_unsupported.dart'
139
if (dart.library.io) 'package:flutter_html/src/widgets/iframe_mobile.dart'
1410
if (dart.library.html) 'package:flutter_html/src/widgets/iframe_web.dart';
15-
import 'package:flutter_html/style.dart';
1611
import 'package:flutter_math_fork/flutter_math.dart';
1712
import 'package:flutter_svg/flutter_svg.dart';
1813
import 'package:html/dom.dart' as dom;
@@ -124,21 +119,25 @@ class AudioContentElement extends ReplacedElement {
124119

125120
@override
126121
Widget toWidget(RenderContext context) {
122+
final VideoPlayerController audioController = VideoPlayerController.network(
123+
src.first ?? "",
124+
);
125+
final ChewieAudioController chewieAudioController = ChewieAudioController(
126+
videoPlayerController: audioController,
127+
autoPlay: autoplay,
128+
looping: loop,
129+
showControls: showControls,
130+
autoInitialize: true,
131+
);
132+
context.parser.root?.addController(element.hashCode, audioController, isAudioController: true);
133+
context.parser.root?.addController(element.hashCode, chewieAudioController);
127134
return Container(
128135
key: AnchorKey.of(context.parser.key, this),
129136
width: context.style.width ?? 300,
130137
height: Theme.of(context.buildContext).platform == TargetPlatform.android
131138
? 48 : 75,
132139
child: ChewieAudio(
133-
controller: ChewieAudioController(
134-
videoPlayerController: VideoPlayerController.network(
135-
src.first ?? "",
136-
),
137-
autoPlay: autoplay,
138-
looping: loop,
139-
showControls: showControls,
140-
autoInitialize: true,
141-
),
140+
controller: chewieAudioController,
142141
),
143142
);
144143
}
@@ -172,24 +171,28 @@ class VideoContentElement extends ReplacedElement {
172171
Widget toWidget(RenderContext context) {
173172
final double _width = width ?? (height ?? 150) * 2;
174173
final double _height = height ?? (width ?? 300) / 2;
174+
final VideoPlayerController videoController = VideoPlayerController.network(
175+
src.first ?? "",
176+
);
177+
final ChewieController chewieController = ChewieController(
178+
videoPlayerController: videoController,
179+
placeholder: poster != null && poster!.isNotEmpty
180+
? Image.network(poster!)
181+
: Container(color: Colors.black),
182+
autoPlay: autoplay,
183+
looping: loop,
184+
showControls: showControls,
185+
autoInitialize: true,
186+
aspectRatio: _width / _height,
187+
);
188+
context.parser.root?.addController(element.hashCode, videoController);
189+
context.parser.root?.addController(element.hashCode, chewieController);
175190
return AspectRatio(
176191
aspectRatio: _width / _height,
177192
child: Container(
178193
key: AnchorKey.of(context.parser.key, this),
179194
child: Chewie(
180-
controller: ChewieController(
181-
videoPlayerController: VideoPlayerController.network(
182-
src.first ?? "",
183-
),
184-
placeholder: poster != null && poster!.isNotEmpty
185-
? Image.network(poster!)
186-
: Container(color: Colors.black),
187-
autoPlay: autoplay,
188-
looping: loop,
189-
showControls: showControls,
190-
autoInitialize: true,
191-
aspectRatio: _width / _height,
192-
),
195+
controller: chewieController,
193196
),
194197
),
195198
);

0 commit comments

Comments
 (0)