之前已经发了一篇博客简述了如何阅读这个项目,尽管这个项目已经开源很久了,但我找了很久都没有找到怎么从播放列表移除歌曲,那就自己动手实现,再提个 PR 吧。

运行起来应用后通过 Inspector(Ctrl+Shift+I)找到希望放置按钮的位置:专辑按钮的旁边。

第一步就是修改UI文件,把这个按钮显示出来。照葫芦画瓢,先加一个按钮。

<child>
<object class="GtkButton" id="remove_button">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="icon-name">app-remove-symbolic</property>
<property name="tooltip-text" translatable="yes">Remove song</property>
<signal name="clicked" handler="remove_button_clicked_cb" swapped="true" />
<style>
<class name="flat" />
</style>
</object>
</child>

每一个显示歌曲信息的行通常都有这样几个按钮:艺术家、收藏、专辑。然而在专辑的详细信息里面歌曲的信息就不需要专辑按钮了。行 Widget 是通用的,但是行中的一些 widget 是应该隐藏的。也就是说,移除歌曲按钮应该只在播放列表中才是可见的,移除按钮只显示在下面这个提交中实现:

https://github.com/wngtk/netease-cloud-music-gtk/commit/cb0a143f01c8d02a58b073677b94c14db0c18dfd

原来的代码中有大量的代码拷贝,我添加新的按钮的时候也尽可能的保持其他的地方不变,因此我需要在多处做拷贝粘贴再修改一下字符,导致了两处忘记修改而调试了两个小时。

而单独这一个 commit 中还没有很好的隐藏按钮,因为在代码拷贝的过程中有两处没有改名字,

f3d90c中修改好后这个按钮才显示在合理的地方。

第二步,实现移除正在播放歌曲。几乎所有的按钮的操作最后都是通过发送一个消息来通知 process_action(),因此我们也需要加入一个新的Action

有了新 Action 还只是能响应按钮,真正的业务逻辑还没实现。在这里简述实现的流程:

  1. 如果移除的歌曲是正在播放的歌曲,并且移除后还有歌曲可以播放,那么播放下一曲
  2. 从播放列表中移除想要移除的歌曲
  3. 更新播放列表的展示

流程确实简单,但是在实现的过程中遇到的细节却困扰了我很久。一个主要的问题是数据的更新并不会引发视图的更新。我需要指定调用更新才能更新视图。因为播放列表中完全存了另外一份数据,同样的歌曲列表的视图,和同样歌曲列表,播放列表视图完全拷贝了一份,所以要实现更新且不破坏原来的代码我只能重新初始化页面。

在胡乱加代码以测试到底为什么之前的修改没有失效的时候,还错误的设置了一个不存在的属性,引入了一个 bug,不过在调试器的帮助下轻松定位到崩溃发生的地方,删除不该有的代码便修复了。

总的来说实现过程是曲折的。最后附上一些在改代码的时候的吐槽。

点我看吐槽

因为代码大量使用了异步,调试的时候还是会有一些不方便,没有办法线性的跟踪,只能在处理信号的地方打断点,但是不知道信号是从哪里发起的。我的溯源的方法是查看按钮的回调函数,但是当自己尝试调用原有的 player_controls 的方法,就找不到额外的信号是怎么被发起的,例如我调用 player_controls 里面的 play() 方法播放器会发起一个播放下一曲的信号,但是实际上 play() 方法里面没有做下一曲的事情,那就是其他的注册的信号处理函数导致了播放下一曲,但是一时半会儿找不到具体的问题所在。或许响应按键操作用异步没什么不对,但是现在的代码中整个播放器的业务逻辑也依赖于异步通道(async_channel)传递信息。

因此,在现有的代码中,单纯的给 plyer_controls 指定一个歌曲去 play 不能正确做到,因为其中可能引发其他的异步信号,导致代码不能按照预期执行。这算是有隐藏的控制流。

作者没有使用 MVC,播放器的功能和 UI 是强耦合的,切换下一曲严重依赖图形界面上的按钮的回调函数。要避免意外情况出现,所有的功能,最好通过模拟 UI 操作进行(尽管这样不好,但是原来的代码里面就是这样做的)。尽管所有的异步的信号最终都会汇聚到 process_action() 函数里面来处理,但是如果单纯的操作 Action 往往和期望不一样,因为 Action 执行之前,有一些副作用发生。不通过模拟 UI 操作来实现移除歌曲的功能,实现的效果往往不如人意。例如

很遗憾,原有的代码并没有做视图(View)和数据(Model)的绑定, 数据改变后,视图并不能自动更新。换句话说,在删除播放列表的歌曲的业务逻辑实现后,UI 并没有自动更新。原来的代码用的是 ListBox,

花时间最多的部分是界面的显示,标记正在播放的图标要显示在正确的行,删除了的歌曲应该不再展示,删除播放列表歌曲的过程中导致歌词不显示了。在已有的接口和设计上,播放列表(playlist_lyrics)可能没有考虑过要修改,播放列表就提供的方法有:初始化,更新歌词,切换播放行。

脑袋中想着既然这里没有 MVC, 我就要手动更新视图。因此我尝试直接修改表达视图的对象,可惜的是最终以失败告终,因为一直存在着小问题。或许那种方法的效果是可行的,但是并没有很好的将已有的代码利用起来。因为整个播放器的控制是由 player_controls 和 window 两个对象控制的,有的操作放到 player_controls 里面完成更方便,而为了利用现有的 window 对象已有的方法有的操作放到 window 会更方便。如果严格一点按照设计模式来做,PlyerControl 应该负责所有的数据的控制,不应该有些东西放到了 window 去做。

而移除的歌曲不再显示是通过手动做页面路由,将 Action::ToSongListPage 执行的部分逻辑再执行一次,而不退出当前的播放列表页面。为了避免破坏原有的代码,不修改 Action::ToSongListPage,然而即便是模拟了重新进入页面,依旧会出现没有正确标记正在播放的歌曲。我没有找到问题的根源,因为一切看起来都是正常的,也没有什么头绪去定位问题到底是什么。

最后没有正确标记的解决方法是由 player_controls 移除歌曲后发起一个Action::UpdatePlayListStatus(playlist.get_position(),因为 player_controls 已经有代码使用了 playlist.get_position(),而 UpdatePlayListStatus 又需要一个 position,就只好将这一部分的代码放在 player_controls。相当于是最后又委派给全局来实现。这里的做法来自于下一曲按钮的回调函数会发送 UpdatePlayListStatus。

sender
.send_blocking(
Action::UpdatePlayListStatus(playlist.get_position())
).unwrap();

改进NeteaseCloudMusicGtk4:添加移除歌曲按钮的更多相关文章

  1. VC6IDE环境宏辅助添加移除注释

    VC6很老了(15年),当年的IDE功能不如现在的各种IDE功能丰富. 比如自动添加注释,就需要借助第三方插件或自己动手实现. 最近做些code试验,新装上了VC6,但是改代码时不能自动添加注释,很不 ...

  2. a标签添加移除事件及开启禁用事件

    一.添加移除点击事件 <script type="text/javascript" src="jquery.min.js"></script& ...

  3. Android标题栏上添加多个Menu按钮

    最近项目中碰到要在Android Menu旁边再添加一个按钮,而不是点击menu按钮然后在弹出一些选项. MainActivity代码: public class MainActivity exten ...

  4. magento产品成功添加到购物车后跳转到不同页面 添加 add to cart 按钮

    1 添加产品到购物车成功后是跳转到购物车页面或不跳转.这个在后台可以设置 system -> configuration -> After Adding a Product Redirec ...

  5. 如何为 Drupal 7 网站添加悬浮的反馈按钮?

    最近有客户咨询我们要怎么为 Drupal 网站添加悬浮按钮,方便访客能够链接到反馈表单页面.很幸运,使用 Feedback Simple 模块可以很容易实现. 在这篇短教程中,我将和大家分享如何添加链 ...

  6. 6. 添加messager.alert()确定按钮的回调函数,即点完确定按钮后触发的事件

    添加messager.alert()确定按钮的回调函数,即点完确定按钮后触发的事件: $.messager.alert('提示信息', "请联系管理员处理!", 'info', f ...

  7. WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮

    原文:WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮 在边框中加入一些元素,在应用程序的界面设计中,已经开始流行起来.特别是在浏览器(Crome,IE,Firefox,Opera)中都有应用. ...

  8. 海思3531添加移远EC20 4g模块(转)

    源: 海思3531添加移远EC20 4g模块 Hi3798移植4G模块(移远EC20)

  9. Jquery添加移除样式

    获取与设置样式 获取class和设置class都可以使用attr()方法来完成.例如使用attr()方法来获取p元素的class,JQuery代码如下: var p_class = $("p ...

  10. JS添加/移除事件

    事件的传播方式 <div id="father"> <div id="son"></div> </div> &l ...

随机推荐

  1. pip安装MySQLdb报错mysql_config not found

    报错EnvironmentError: mysql_config not found解决方法 1.sudo apt-get install python-setuptools 2.sudo apt-g ...

  2. GUI编程之AWT

    介绍 包含了很多类和接口 元素:窗口.按钮.文本框 java.awt Frame 就是一个窗口 实现 package com.yeyue.lesson01;​import java.awt.*;​pu ...

  3. Deepseek学习随笔(3)--- 高效提问技巧

    明确需求 在与 DeepSeek 互动时,明确需求是获取高质量回复的关键.以下是一些示例: 错误示例:帮我写点东西 这样模糊的指令无法让 DeepSeek 理解你的具体需求,生成的回复可能无法满足你的 ...

  4. autMan奥特曼机器人--可爱猫对接微信教程

    教程开始 文章底部下载可爱猫框架以及对应的微信版本 1.安装3.4.0.38版本微信,解压可爱猫框架压缩包 如果微信安装了高于3.4.0.38的版本,请先卸载 2.打开可爱猫框架,会弹微信扫码登录,机 ...

  5. stream流中toMap()api和Duplicate key问题

    1.指定key-value,value是对象中的某个属性值. Map<Integer,String> userMap = userList.stream().collect(Collect ...

  6. mysql 卸载安装教程链接

    https://blog.csdn.net/weixin_56952690/article/details/129678685 https://blog.51cto.com/u_16213646/70 ...

  7. svn提示Node remains in conflict的解决办法

    svn 更新提示Node remains in conflict 这个时候不管svn up多少次,都无法更新到最新的内容 解决办法: svn revert --depth=infinity * 其中* ...

  8. python3 ModuleNotFoundError: No module named 'CommandNotFound'

    前言 python3 报错:ModuleNotFoundError: No module named 'CommandNotFound' 这是 linux 安装多版本 python 时的一个遗留问题, ...

  9. docker删除所有服务service,停止并删除所有容器container

    查看运行容器docker ps 查看所有容器docker ps -a 进入容器其中字符串为容器ID:docker exec -it d27bd3008ad9 /bin/bash 删除所有服务:dock ...

  10. RabbitMQ 延迟任务(限时订单) 思路

    一.场景 我们经常会碰见,一个需求就是,发送一条指令(消息),延迟一段时间执行,比如说常见的淘宝当下了一个订单后,订单支付时间为半个小时,如果半个小时没有支付,则关闭该订单.当然实现的方式有几种,今天 ...