Switch media to another player

This commit is contained in:
estevez-dev
2019-09-15 17:29:49 +03:00
parent 8fc7d0b61e
commit 340e8569cc
7 changed files with 109 additions and 50 deletions

View File

@ -74,10 +74,37 @@ class MediaPlayerEntity extends Entity {
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list"); List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
List<String> get sourceList => getStringListAttributeValue("source_list"); List<String> get sourceList => getStringListAttributeValue("source_list");
DateTime get positionLastUpdated => DateTime.tryParse("${attributes["media_position_updated_at"]}")?.toLocal();
int get durationSeconds => _getIntAttributeValue("media_duration");
int get positionSeconds => _getIntAttributeValue("media_position");
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {
return MediaPlayerControls(); return MediaPlayerControls();
} }
bool canCalculateActualPosition() {
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null;
}
double getActualPosition() {
double result = 0;
if (canCalculateActualPosition()) {
Duration durationD;
Duration positionD;
durationD = Duration(seconds: durationSeconds);
positionD = Duration(
seconds: positionSeconds);
result = positionD.inSeconds.toDouble();
int differenceInSeconds = DateTime
.now()
.difference(positionLastUpdated)
.inSeconds;
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
}
return result;
}
} }

View File

@ -23,26 +23,10 @@ class _MediaPlayerProgressBarState extends State<MediaPlayerProgressBar> {
final EntityModel entityModel = EntityModel.of(context); final EntityModel entityModel = EntityModel.of(context);
final MediaPlayerEntity entity = entityModel.entityWrapper.entity; final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
double progress; double progress;
DateTime lastUpdated = DateTime.tryParse("${entity.attributes["media_position_updated_at"]}")?.toLocal(); int currentPosition;
Duration duration; if (entity.canCalculateActualPosition()) {
Duration position; currentPosition = entity.getActualPosition().toInt();
int durationInSeconds = entity._getIntAttributeValue("media_duration"); progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
if (durationInSeconds != null) {
duration = Duration(seconds: durationInSeconds);
}
int positionInSeconds = entity._getIntAttributeValue("media_position");
if (positionInSeconds != null) {
position = Duration(
seconds: positionInSeconds);
}
if (lastUpdated != null && duration != null && position != null) {
int currentPosition = position.inSeconds;
int differenceInSeconds = DateTime
.now()
.difference(lastUpdated)
.inSeconds;
currentPosition = currentPosition + differenceInSeconds;
progress = (currentPosition <= duration.inSeconds) ? currentPosition / duration.inSeconds : 100;
} else { } else {
progress = 0; progress = 0;
} }

View File

@ -11,6 +11,8 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
bool _seekStarted = false; bool _seekStarted = false;
bool _changedHere = false; bool _changedHere = false;
double _currentPosition = 0; double _currentPosition = 0;
int _savedPosition = 0;
final TextStyle _seekTextStyle = TextStyle( final TextStyle _seekTextStyle = TextStyle(
fontSize: 20, fontSize: 20,
color: Colors.blue, color: Colors.blue,
@ -20,6 +22,10 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
@override @override
initState() { initState() {
super.initState(); super.initState();
if (HomeAssistant().savedPlayerPosition != null) {
_savedPosition = HomeAssistant().savedPlayerPosition;
HomeAssistant().savedPlayerPosition = null;
}
_timer = Timer.periodic(Duration(seconds: 1), (_) { _timer = Timer.periodic(Duration(seconds: 1), (_) {
if (!_seekStarted && !_changedHere) { if (!_seekStarted && !_changedHere) {
setState(() {}); setState(() {});
@ -27,35 +33,57 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
}); });
} }
void _sendTo(entity) {
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
HomeAssistant().savedPlayerId = entity.entityId;
Navigator.of(context).pushNamed("/play-media", arguments: {"url": entity.attributes["media_content_id"], "type": entity.attributes["media_content_type"]});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final EntityModel entityModel = EntityModel.of(context); final EntityModel entityModel = EntityModel.of(context);
final MediaPlayerEntity entity = entityModel.entityWrapper.entity; final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
DateTime lastUpdated = DateTime.tryParse("${
entity.attributes["media_position_updated_at"]}")?.toLocal(); if (entity.canCalculateActualPosition()) {
Duration duration; if (HomeAssistant().savedPlayerId != entity.entityId && HomeAssistant().savedPlayerPosition != null) {
Duration position; _savedPosition = HomeAssistant().savedPlayerPosition;
int durationInSeconds = entity._getIntAttributeValue("media_duration"); HomeAssistant().savedPlayerPosition = null;
if (durationInSeconds != null) { }
duration = Duration(seconds: durationInSeconds);
}
int positionInSeconds = entity._getIntAttributeValue("media_position");
if (positionInSeconds != null) {
position = Duration(
seconds: positionInSeconds);
}
if (lastUpdated != null && duration != null && position != null) {
if (entity.state == EntityState.playing && !_seekStarted && if (entity.state == EntityState.playing && !_seekStarted &&
!_changedHere) { !_changedHere) {
_currentPosition = position.inSeconds.toDouble(); _currentPosition = entity.getActualPosition();
int differenceInSeconds = DateTime
.now()
.difference(lastUpdated)
.inSeconds;
_currentPosition = ((_currentPosition + differenceInSeconds) <= duration.inSeconds) ? (_currentPosition + differenceInSeconds) : duration.inSeconds.toDouble();
} else if (_changedHere) { } else if (_changedHere) {
_changedHere = false; _changedHere = false;
} }
List<Widget> buttons = [];
if (_savedPosition > 0) {
buttons.add(
RaisedButton(
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
color: Colors.orange,
focusColor: Colors.white,
onPressed: () {
eventBus.fire(ServiceCallEvent(
"media_player",
"media_seek",
"${entity.entityId}",
{"seek_position": _savedPosition}
));
setState(() {
_savedPosition = 0;
});
},
)
);
}
buttons.add(
RaisedButton(
child: Text("Send to another player..."),
color: Colors.blue,
textColor: Colors.white,
onPressed: () => _sendTo(entity),
)
);
return Padding( return Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, 20, Sizes.rightWidgetPadding, 0), padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, 20, Sizes.rightWidgetPadding, 0),
child: Column( child: Column(
@ -68,7 +96,7 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
Expanded( Expanded(
child: Text("${Duration(seconds: _currentPosition.toInt()).toString().split(".")[0]}",textAlign: TextAlign.center, style: _seekTextStyle), child: Text("${Duration(seconds: _currentPosition.toInt()).toString().split(".")[0]}",textAlign: TextAlign.center, style: _seekTextStyle),
), ),
Text("${duration.toString().split(".")[0]}") Text("${Duration(seconds: entity.durationSeconds).toString().split(".")[0]}")
], ],
), ),
Container(height: 10,), Container(height: 10,),
@ -76,7 +104,7 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
min: 0, min: 0,
activeColor: Colors.amber, activeColor: Colors.amber,
inactiveColor: Colors.black26, inactiveColor: Colors.black26,
max: duration.inSeconds.toDouble(), max: entity.durationSeconds.toDouble(),
value: _currentPosition, value: _currentPosition,
onChangeStart: (val) { onChangeStart: (val) {
_seekStarted = true; _seekStarted = true;
@ -103,6 +131,9 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
} }
}); });
}, },
),
ButtonBar(
children: buttons,
) )
], ],
), ),

View File

@ -14,6 +14,8 @@ class HomeAssistant {
Map services; Map services;
String _userName; String _userName;
HSVColor savedColor; HSVColor savedColor;
int savedPlayerPosition;
String savedPlayerId;
String fcmToken; String fcmToken;

View File

@ -173,7 +173,10 @@ class HAClientApp extends StatelessWidget {
"/": (context) => MainPage(title: 'HA Client'), "/": (context) => MainPage(title: 'HA Client'),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"), "/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
"/putchase": (context) => PurchasePage(title: "Support app development"), "/putchase": (context) => PurchasePage(title: "Support app development"),
"/play-media": (context) => PlayMediaPage(mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",), "/play-media": (context) => PlayMediaPage(
mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",
mediaType: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['type'] ?? '' : ''}",
),
"/log-view": (context) => LogViewPage(title: "Log"), "/log-view": (context) => LogViewPage(title: "Log"),
"/login": (context) => WebviewScaffold( "/login": (context) => WebviewScaffold(
url: "${ConnectionManager().oauthUrl}", url: "${ConnectionManager().oauthUrl}",

View File

@ -349,6 +349,7 @@ class ConnectionManager {
} }
Future callService({String domain, String service, String entityId, Map additionalServiceData}) { Future callService({String domain, String service, String entityId, Map additionalServiceData}) {
Completer completer = Completer();
Map serviceData = {}; Map serviceData = {};
if (entityId != null) { if (entityId != null) {
serviceData["entity_id"] = entityId; serviceData["entity_id"] = entityId;
@ -357,16 +358,17 @@ class ConnectionManager {
serviceData.addAll(additionalServiceData); serviceData.addAll(additionalServiceData);
} }
if (serviceData.isNotEmpty) if (serviceData.isNotEmpty)
return sendHTTPPost( sendHTTPPost(
endPoint: "/api/services/$domain/$service", endPoint: "/api/services/$domain/$service",
data: json.encode(serviceData) data: json.encode(serviceData)
); ).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError("${e["message"]}")));
//return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service, "service_data": serviceData}); //return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service, "service_data": serviceData});
else else
return sendHTTPPost( sendHTTPPost(
endPoint: "/api/services/$domain/$service" endPoint: "/api/services/$domain/$service"
); ).then((data) => completer.complete(data)).catchError((e) => completer.completeError(HAError("${e["message"]}")));;
//return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service}); //return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service});
return completer.future;
} }
Future<List> getHistory(String entityId) async { Future<List> getHistory(String entityId) async {

View File

@ -3,8 +3,9 @@ part of '../main.dart';
class PlayMediaPage extends StatefulWidget { class PlayMediaPage extends StatefulWidget {
final String mediaUrl; final String mediaUrl;
final String mediaType;
PlayMediaPage({Key key, this.mediaUrl}) : super(key: key); PlayMediaPage({Key key, this.mediaUrl, this.mediaType}) : super(key: key);
@override @override
_PlayMediaPageState createState() => new _PlayMediaPageState(); _PlayMediaPageState createState() => new _PlayMediaPageState();
@ -22,13 +23,22 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
bool _isMediaExtractorExist = false; bool _isMediaExtractorExist = false;
StreamSubscription _stateSubscription; StreamSubscription _stateSubscription;
StreamSubscription _refreshDataSubscription; StreamSubscription _refreshDataSubscription;
final List<String> _contentTypes = ["movie", "video", "music", "image", "image/jpg", "playlist"]; List<String> _contentTypes = ["movie", "video", "music", "image", "image/jpg", "playlist"];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_mediaUrl = widget.mediaUrl; _mediaUrl = widget.mediaUrl;
_contentType = _contentTypes[0]; if (widget.mediaType.isNotEmpty) {
if (!_contentTypes.contains(widget.mediaType)) {
_contentTypes.insert(0, widget.mediaType);
_contentType = _contentTypes[0];
} else {
_contentType = widget.mediaType;
}
} else {
_contentType = _contentTypes[0];
}
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
if (event.entityId.contains("media_player")) { if (event.entityId.contains("media_player")) {
Logger.d("State change event handled by play media page: ${event.entityId}"); Logger.d("State change event handled by play media page: ${event.entityId}");