[转载][Android]模拟器现已支持 AMD 处理器和 Hyper-V

原创: Google Play 谷歌开发者 Yesterday
作者:Jamal Eason, Android 产品经理

自两年前谷歌对 Android 模拟器进行重大更新以来,我们一直致力于开发出一款运行速度快、功能全面的模拟器,帮助您为用户打造卓越应用体验。Android 模拟器现已成为 Android Studio 中最受欢迎的设备 —— 使用量为 Android 实体设备的两倍以上。很高兴看到 Android 模拟器一路以来陪伴各位开发者共同成长,但是旅程才刚刚开始,我们还可能做得更好。

模拟器速度一直是 Android Studio 团队重点攻克的难题之一:在之前的几个版本中,我们相继加入快速启动和模拟器快照功能,让开发者能够在 2 秒内快速启动模拟器并恢复之前的会话。迄今为止,Android 模拟器已能够在 macOS® 以及 Linux 设备上流畅运行,但对 Windows 或者 Hyper-V 平台用户而言并非如此,Android 模拟器支持只在英特尔处理器上提供的硬件加速增强。通过在 Android 模拟器添加 AMD 处理器以及 Hyper-V 虚拟机的支持,我们在本次版本更新中顺利解决了开发者社区里这两项存在已久的用户请求。

>> 模拟器快照功能链接:
https://developer.android.google.cn/studio/run/emulator#snapshots

今天,您就可以下载最新版本 Android 模拟器,在搭载 AMD 处理器的电脑上运行 Android x86 虚拟设备。本次重要更新同时也会让应用开发者们更容易接入 Android 模拟器,不仅不会受到此前的软件模拟上的限制,还会获得硬件加速性能支持。而且,对于那些希望利用 Hyper-V 运行自己本地应用后端的用户来说,现在的 Android 模拟器也可以和 Windows 10 上 Hyper-V 支持的其他应用兼容运行。

>> 最新版本 Android 模拟器链接:
https://developer.android.google.cn/studio/run/emulator#install

得益于新的 Windows 虚拟化管理平台 (WHPX) API 以及微软开源项目上作出的努力,更多 Android 应用开发者能够体验到模拟器在速度以及功能方面的显著改进。

>> Windows 虚拟化管理平台链接:
https://docs.microsoft.com/en-us/virtualization/api/

以上技术支持最早在 Android 模拟器 v27.3.8 (金丝雀版本) 中试行,而现在我们将这些预览版特性 (AMD 处理器以及 Hyper-V 支持) 推广至稳定版,希望获得更多反馈。此外,我们还提升了模拟器快照的加载速度,让使用英特尔硬件加速执行管理器(HAXM) 的开发者将获得更好体验。

如何使用

Linux系统

若您正在使用 Linux 进行 Android 应用开发,Android 模拟器将继续使用原生 KVM 虚拟技术管理工具为英特尔以及 AMD 设备提供高速、高性能的虚拟化解决方案。Android 模拟器 v27.3.8 新增加快照用户界面,并在性能、稳定性和资源利用方面的表现更为出色。

>> KVM 虚拟技术管理工具链接:
https://www.linux-kvm.org/page/Main_Page

macOS系统

若系统为 OS X v10.10 Yosemite 或更高版本,Android 模拟器在默认情况下继续使用内置 Hypervisor.Framework,且在框架无法启动的情况下 (如系统为 OS X v10.9 或更低版本),转用英特尔硬件加速执行管理器 (HAXM)。在升级至最新 macOS 版模拟器之后,您可以使用新增的快照用户界面,并享受到性能更好、稳定性更强的 Android 模拟器。

>> Hypervisor.Framework 链接:
https://developer.apple.com/documentation/hypervisor

微软 Windows 系统

对于使用英特尔 x86 处理器的设备来说,默认情况下 Android 模拟器将继续使用硬件加速执行管理器技术 (Intel HAXM)。该技术是英特尔开发的一款较为成熟的开源虚拟化技术解决方案。此外,由于英特尔在创新研发方面的持续投入,HAXM 依旧是目前市面上最快的 Android 模拟器加速技术。请前往 Android SDK 管理器页面查看更新,下载最新版本英特尔 HAXM v7.2.0。

若您的设备使用的是 AMD 处理器,需同时满足以下条件:

AMD 处理器 —— 推荐使用 AMD 锐龙系列处理器;

Android Studio 3.2 Beta 或更高版本,点击前往 Android Studio 预览版下载页面;

Android 模拟器 v27.3.8 +,点击前往 Android Studio SDK 管理器页面下载;

x86 Android 虚拟设备 (AVD),创建虚拟设备;

Windows 10 Version 1803 四月更新版;

在 Windows 功能菜单中勾选 “Windows Hypervisor Platform”

>> Android Studio 预览版链接:
https://developer.android.google.cn/studio/preview/

>> Android Studio SDK 管理器链接:
https://developer.android.google.cn/studio/intro/update#sdk-manager

>> 创建 AVD 链接:
https://developer.android.google.cn/studio/run/managing-avds#createavd

如果您想在配有英特尔处理器的设备上并行运行 Hyper-V 与 Android 模拟器,请根据上文指示更新 Android Studio 与 Android 模拟器,同时:

在 Windows 功能菜单中勾选 “Hyper-V” —— 仅支持 Windows 10 专业版、教育版与企业版

英特尔处理器:确保您的 Intel Core 处理器支持虚拟化技术 (VT-x)、扩展页表 (ETP) 以及不受限客户机 (UG) 功能;并在 BIOS 管理设置中启用 VT-x 虚拟化选项。

阅读文档 (https://developer.android.google.cn/studio/run/emulator-acceleration),了解更多安装技巧以及错误排查细节。

概括来说:若您的 Windows 设备使用英特尔处理器,Android 模拟器将继续使用英特尔 HAXM 技术 —— 它的速度更快,同时也是我们的推荐配置;若设备使用 AMD 处理器或 Hyper-V 虚拟机进行开发,您也不妨尝试一下新的 Android 模拟器,相信它会给您带去不少惊喜。

[Android][targetSdkVersion]应用程序更新的指定目标必须是Android8.0(API等级26)


最近一次更新APP时,Google Play提示了这个信息。。。
历史的欠账总归要补上,貌似有点麻烦。。。
一步一个脚印,记录targetSdkVersion从21升级到26遇到的问题吧。。。
官方SDK版本更新总览:https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-runtime-permissions
官方TargetSDK说明:https://developer.android.com/distribute/best-practices/develop/target-sdk
中文PDF版:Google Play 目标 API 等级(targetSdkVersion)重要变更要求  _  Android Developers

1、Runtime Permission(在运行时请求权限
这是 Android 6.0(API 级别 23)引进的,运行时态才去检查权限,而不是安装的时候。权限划分为多个保护级别,第三方应用受到下面三个级别的影响:normal, signature, dangerous.

Normal permissions

权限被声明为Normal级别,任何应用都可以申请,在安装应用时,不会直接提示给用户,点击全部才会展示。
截至Android8.1,下面的权限都是正常许可:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MANAGE_OWN_CALLS
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_COMPANION_RUN_IN_BACKGROUND
REQUEST_COMPANION_USE_DATA_IN_BACKGROUND
REQUEST_DELETE_PACKAGES
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
SET_ALARM
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

Signature permissions

权限被声明为Signature级别,只有和该apk(定义了这个权限的apk)用相同的私钥签名的应用才可以申请该权限。

Dangerous permissions

权限被声明为Dangerous级别,任何应用都可以申请,在安装应用时,会直接提示给用户。

android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
android.permission.CAMERA
android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS
android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION
android.permission.RECORD_AUDIO
android.permission.READ_PHONE_STATE
android.permission.READ_PHONE_NUMBERS
android.permission.CALL_PHONE
android.permission.ANSWER_PHONE_CALLS
android.permission.READ_CALL_LOG
android.permission.WRITE_CALL_LOG
android.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.PROCESS_OUTGOING_CALLS
android.permission.BODY_SENSORS
android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_WAP_PUSH
android.permission.RECEIVE_MMS
android.permission.READ_EXTERNAL_STORAGE
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.SYSTEM_ALERT_WINDOW

在manifest文件中查找需要添加权限获取的类型,目前项目中使用了下面几个权限分组:Camera、Storage、Location、Phone。
Camera权限分组:比较简单,在扫描二维码的activity启动之前验证权限即可,代码异步回调调用。
Storage权限分组:需要根据调用的文件路径来区分,如下图所示,外部存储和内部存储的区别

也就是说访问 Environment.getExternalStorageDirectory类型的,都需要加上权限判断(还包括getExternalStoragePublicDirectory函数指定的参数、各种公共目录如DCIM、Music、Pictures、Movies、Downloads、Documents等等)。

2、FileUriExposedException异常
发现以前代码有些用法不对,如果是为了保存文件,那就需要提示要求权限;如果是为了分享文件,则需要改用Android7.0提出的FileProvider,同时在intent中设置Uri的临时权限 FLAG_GRANT_READ_URI_PERMISSION ,并且该权限在对方app退出后自动失效(intent的stack内有效)。
另外选拍照图片是一个特例,原先传入的是DCIM下的文件路径,现在要改成用FileProvider转换成Uri,否则相机会抛出异常:

android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/Camera/IMG_20180713_171929.jpg exposed beyond app through ClipData.Item.getUri()
    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1972)
    at android.net.Uri.checkFileUriExposed(Uri.java:2371)
    at android.content.ClipData.prepareToLeaveProcess(ClipData.java:963)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:10231)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:10216)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1661)
    at android.app.Activity.startActivityForResult(Activity.java:4617)
    at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:48)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:77)
    at android.support.v4.app.ActivityCompatJB.startActivityForResult(ActivityCompatJB.java:26)
    at android.support.v4.app.ActivityCompat.startActivityForResult(ActivityCompat.java:146)
    at android.support.v4.app.FragmentActivity.startActivityFromFragment(FragmentActivity.java:937)
    at android.support.v4.app.FragmentActivity$HostCallbacks.onStartActivityFromFragment(FragmentActivity.java:1046)
    at android.support.v4.app.Fragment.startActivityForResult(Fragment.java:956)
    at android.support.v4.app.Fragment.startActivityForResult(Fragment.java:945)

我们来看看FileUriExposedException是个什么鬼。。。

The exception that is thrown when an application exposes a file:// Uri to another app.

This exposure is discouraged since the receiving app may not have access to the shared path. For example, the receiving app may not have requested the Manifest.permission.READ_EXTERNAL_STORAGE runtime permission, or the platform may be sharing the Uri across user profile boundaries.

Instead, apps should use content:// Uris so the platform can extend temporary permission for the receiving app to access the resource.

This is only thrown for applications targeting Build.VERSION_CODES.N or higher. Applications targeting earlier SDK versions are allowed to share file:// Uri, but it’s strongly discouraged.

总而言之,就是Android不再允许在app中把file://Uri暴露给其他app,包括但不局限于通过Intent或ClipData 等方法。

public static Uri fixFileProviderUri(Context context, File file, Intent action) {
        // FileProvider修正文件uri
        Uri uri = FileProvider.getUriForFile(context,
                context.getString(R.string.file_provider_auth), file);
        action.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        return uri;
    }

通过FileProvider提供的getUriForFile,将file://路径转成content://,注意!这里需要加上FLAG_GRANT_WRITE_URI_PERMISSION,同时APP应该请求Camera和Storage权限。。。。。。
拍照和选图片搞定了,结果裁剪图片又出了问题,原来APP提供出去的路径,需要给裁剪APP赋值可写的权限:

for (ResolveInfo res : list) {
    // API 24 需要赋予每一个裁剪APP访问文件的读写权限
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        // 图片文件只读
        context.getContext().grantUriPermission(res.activityInfo.packageName, imageFile,
            Intent.FLAG_GRANT_READ_URI_PERMISSION);
        // 裁剪输出文件需要可写
        context.getContext().grantUriPermission(res.activityInfo.packageName, uri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
}

这里grantUriPermission如果传入的包名是自身app,等同于上面的addFlags添加的Intent.FLAG_GRANT_READ_URI_PERMISSION。
我滴妈呀,FileProvider的坑真多,Intent分享出去的Uri即要自己可读,又要给别的APP赋予可写的权限。。。。。。
参考文档:http://gelitenight.github.io/android/2017/01/29/solve-FileUriExposedException-caused-by-file-uri-with-FileProvider.html
链接打不开可以点PDF文档快照:使用FileProvider解决file___ URI引起的FileUriExposedException

如果FileProvider的坑到这里就结束了,我会灰常开森。。。
下面介绍由content://引申的又一个坑,由于Intent之间传递禁止了file://头,所以APP内部传递文件句柄,也必须经FileProvider的手,那么在onActivityResult得到的Uri也是content://,大家可能会想,以前拿到这种类型已经有开源的方法嘛,通过context.getContentResolver().query查询column = “_data”得到真实的文件路径。。。可惜抛出了异常:

    Caused by: java.lang.IllegalArgumentException: column '_data' does not exist

查阅官方对FileProvider的详细解释,它给出了标准的流程如下(Access the requested file):

    /*
     * When the Activity of the app that hosts files sets a result and calls
     * finish(), this method is invoked. The returned Intent contains the
     * content URI of a selected file. The result code indicates if the
     * selection worked or not.
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode,
            Intent returnIntent) {
        // If the selection didn't work
        if (resultCode != RESULT_OK) {
            // Exit without doing anything else
            return;
        } else {
            // Get the file's content URI from the incoming Intent
            Uri returnUri = returnIntent.getData();
            /*
             * Try to open the file for "read" access using the
             * returned URI. If the file isn't found, write to the
             * error log and return.
             */
            try {
                /*
                 * Get the content resolver instance for this context, and use it
                 * to get a ParcelFileDescriptor for the file.
                 */
                mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                Log.e("MainActivity", "File not found.");
                return;
            }
            // Get a regular file descriptor for the file
            FileDescriptor fd = mInputPFD.getFileDescriptor();
            ...
        }
    }

上面这段话的核心意思,就是通过getContentResolver().openFileDescriptor持有Intent传递过来的文件句柄,已经赋予了相应的读写权限。
So,到底怎么搞好呀?如果只是简单的拿文件,直接读数据倒简单;不过大部分代码应该是存储了文件路径,并且在APP关闭或者手机重启之后继续访问;
这个问题有点棘手,尽量在onActivityResult之后立即处理数据,否则拷贝文件带来性能开销、管理文件也需要额外处理。
唔,步子迈的太大会扯着蛋。。。考虑到APP内部跳转Intent携带的Uri路径是以本包名格式固定的,特别处理一下算了。
特别的举例:EXTERNAL_DIR,一般图省事,在res/xml/filepaths.xml中会定义外部存储的根路径

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- root-path/files-path/cache-path/external-path -->
    <external-path path="/" name="EXTERNAL_DIR" />
</paths>

那么转换Uri得到的路径是这样子的:

    content://包名.fileprovider/EXTERNAL_DIR/xxxx

So,我们通过Environment.getExternalStorageDirectory得到根路径,替代content://包名.fileprovider/EXTERNAL_DIR部分,就拿到了真实的文件路径。
哦,顺嘴提一句,上面这个函数虽然要求Storage权限,但是实际上无权限时,也能得到路径,只是不能访问读写文件而已。
注意,这样的替换只能针对APP内部Intent跳转,并且传递文件路径是在External Storage下的!!!

[m3u8]关于AES-128解码的尝试

上篇文章中m3u8文件采用了AES-128的加密方式,key是16位,还有iv参数,看样子是可以将ts解码出来,直接能播放。
传统的采用pycrypto模块
pip install pycrypto

示例代码如下:

    >>> from Crypto.Cipher import AES
    >>> from Crypto import Random
    >>>
    >>> key = b'Sixteen byte key'
    >>> iv = Random.new().read(AES.block_size)
    >>> cipher = AES.new(key, AES.MODE_CFB, iv)
    >>> msg = iv + cipher.encrypt(b'Attack at dawn')

这里的key是16位的byte,iv也是16位byte,上文中的iv是字符串“0xae98961dd802f860ae9b67dd75136a18”,需要转码

from binascii import unhexlify
iv = unhexlify('ae98961dd802f860ae9b67dd75136a18')

注意去掉字符串前面的0x前缀。

解析m3u8文件有开源的库,这里推荐:
https://github.com/globocom/m3u8.git

采用cipher.decrypt()得到的ts文件和源文件一样大,但是不能直接播放。。。so,肯定是哪里出问题了。。。。

突然发现openssl直接有提供aes-128带key和iv的解码cmd工具,用这个试试:

openssl enc -d -aes-128-cbc -iv {iv的十六进制字符串} -K {key的十六进制字符串} -in {输入ts文件} -out {输出ts文件}

通过OpenSSL这个直接输出的ts文件可以播放,泪流满面。。。。。。

在网上搜了一下这俩的实现:
Implement OpenSSL AES Encryption in Python
文章中提到:
The only non-standard (and most difficult) part is the derivation of the IV and the key from the password.

关于IV和key的不是标准部分…

OpenSSL puts and expects the salt in the first 8 bytes of the encrypted payload.

点开EVP_BytesToKey函数,有详细的说明。

难道说openssl需要读取加密的ts文件头8字节作为salt?

[m3u8]解析某个视频网站播放

m3u8视频格式,其实请求下来的是一个文本,里面记载了一串ts视频片段以及时间戳;有些还包含视频加密的key。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-KEY:METHOD=AES-128,URI="http://hls.videocc.net/8b0a2fa267/0/8b0a2fa267c027bc31a2675e51780d00_2.key?pid=1529814332630X1997867&ts=1529814332000&sign=98271bf3deabebb179b9d0476a30f123&ms=5d88e9afe50d7c8248b52b26c15ef328",IV=0xae98961dd802f860ae9b67dd75136a18
#EXT-X-TARGETDURATION:13
#EXTINF:5.080000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_0.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:5.000000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_1.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:10.000000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_2.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:10.000000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_3.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:10.000000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_4.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:10.000000,
http://ab-mts.vid+eocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_5.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:12.760000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_6.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXTINF:0.200000,
http://ab-mts.videocc.net/8b0a2fa267/48/1527235156000/0/78/0d/00_2/8b0a2fa267c027bc31a2675e51780d00_2_7.ts?pid=1529814332630X1997867&ts=1529814332000&sign=5d88e9afe50d7c8248b52b26c15ef328
#EXT-X-ENDLIST

某个视频网站只提供在线播放,并且只能用它的js播放,通过请求ts和sign以及验证parent.origin来源限制domain是视频拥有者设置好的域名来源。

原本计划拿到视频vid,可以直接请求ts和sign,再本地改改hosts映射就可以播放了;
后来想了一下,m3u8视频播放是一个公用标准,应该将key和ts片段下载了,就能自建server播放。
So,试试看。

1、首先在Github上淘一淘,有hls.js:
https://video-dev.github.io/hls.js/docs/html/
噢,居然还有m3u8的python解析:
https://github.com/globocom/m3u8
这下播放和修改m3u8替换url都方便了。
2、计划任务:
通过mitmproxy代理,获取svideoid的列表,是json数据;
通过svideo得到sign、ts
构建m3u8的下载地址,还需要生成pid

    e.getPid = function t() {
        var e = new Date;
        var t = e.getTime() + "";
        var n = parseInt(Math.random() * 1e6 + 1e6) + "";
        var i = t + "X" + n;
        if (typeof updatePid == "function") {
            updatePid(i)
        }
        return i
    }

下载原生的m3u8文件后,用上面的工具解析,然后替换url,保存到自己server的路径;
下载每一条ts文件,保存到自定义路径;
这里可以用video的唯一编码来作为文件夹,以清晰度为文件名前缀,ts分片索引保存;
3、数据到位之后,编写html播放页面

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<!-- Or if you want a more recent canary version -->
<!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
<video id="video"></video>
<script>
  var video = document.getElementById('video');
  if(Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
  });
 }
 // hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.
 // When the browser has built-in HLS support (check using `canPlayType`), we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video element throught the `src` property.
 // This is using the built-in support of the plain video element, without using hls.js.
 // Note: it would be more normal to wait on the 'canplay' event below however on Safari (where you are most likely to find built-in HLS support) the video.src URL must be on the user-driven
 // white-list before a 'canplay' event will be emitted; the last video event that can be reliably listened-for when the URL is not on the white-list is 'loadedmetadata'.
  else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = 'https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8';
    video.addEventListener('loadedmetadata',function() {
      video.play();
    });
  }
</script>

经过自建Server的验证,确实key文件和ts文件内容是不变化的,可以下载下来播放;
那么只剩下一个问题:如果从videoId找到m3u8的下载地址?需要研究一下该网站的视频播放器js文件了,筛选可疑的代码如下:

        function Y(t) {
            var n = (new Date).getTime();
            var r = o.vid + n + "polyv";
            var a = CryptoJS.MD5(r);
            var s = "";
            if (o.hlstest) {
                s = i + "hlstest.videocc.net/event/switch_bitrate?sign=" + a + "&ts=" + n + "&vid=" + o.vid + "&mt=" + o.ts + "&ms=" + o.sign
            } else {
                s = i + "hls.videocc.net/event/switch_bitrate?sign=" + a + "&ts=" + n + "&vid=" + o.vid + "&mt=" + o.ts + "&ms=" + o.sign
            }
            e.ajax({
                url: s,
                dataType: "text",
                success: function(e) {
                    if (e == "success") {
                        var n = J(function(e) {
                            t.src = e;
                            t.play()
                        })
                    }
                },
                error: function() {}
            })
        }

这里的i是http协议

            var i = "http://";
            if (window.location.protocol == "https:") {
                i = "https://"
            }

通过Postman发送构建的m3u8的url,视频文件url居然都是相同的层级,OK,这下都不用查找m3u8真实地址了,直接修改videoId即可。
开始编写Python代码:
1、解析VideoList中的svideoId字段
2、获取该svideoId的当前ts、sign
3、构建m3u8下载地址,下载m3u8文本,修改url并存到指定路径
4、下载m3u8中的ts片段,并存到指定路径
5、创建模板html,建立title到svideoId的超链接,点击即可播放。

[转载][人民日报]北京:非京牌车要管起来

北京:非京牌车要管起来
本报记者 朱竞若 王昊男

街头驶过 9 台小轿车,就可能有一台是非京牌的,这就是当下的北京。
数据显示:截至今年4月,北京全市机动车保有量,即北京号牌的机动车达596.8万辆;而目前平均每周,6环内通过办理“进京证”在京使用的外地号牌车辆超70万辆。
北京对非京牌车要不要管起来?这个问题,争论多年。现在,外地车的过度增长 ,“进京证”演变为不用摇号的日常行驶证的现实,使“政府该出手”渐成各界共识。
要不要管?
到了非管不可的时候了
70万辆是什么概念?相当于香港全部汽车保有量。
北京外地车牌高速增长,一是源自本地市民内需,绕过摇号得方便。据北京市交管局统计,这种情况约占总量的15.67%。
“中签太难了,外地牌照办起来简单还不贵。”北京地铁4号线的终点,紧邻北五环。周边几个停车场 ,已停满了车,冀、蒙、晋、豫 ……各地车牌随处可见。开河北牌照车的熊先生告诉记者,2016年,通过熟人介绍,他花4500元办了一张河北牌照,成为有车一族。
据记者了解,目前北京代办外地牌照市场很活跃,根据地域和获得牌照时间不同,一般收费5000元到6000元不等,甚至有些4S店也提供此类服务。
更多的使用者,是在北京打拼的外地人。在老家买个车,开到北京用。
2015年,北京市日均办理车辆进京通行证达5万张。2016年5月,北京市公安局交管局出台便民措施,规定外地客车通过“北京交警”APP,在网上就能办理“进京证”,持电子凭证就可行驶。此后,日均办证迅速增长到10万张以上。2017年以来,平均每周办理72.5万张。
摇不到号的“刚需”,扎根北京的“北漂”;上外地牌门槛低,违法上路成本低。高需求与低成本叠加,在诸多因素的影响下,外地车在北京有了生长的土壤。
据介绍,如果算上北京未加限制的六环外的车辆,在京的非京牌车,实际总数达到100多万辆的惊人数量。作为超大型城市,交通问题没法回避,也不能回避,所以,这一问 题已到了非管不可的时候了。
该不该管?
相关管理措施呼之欲出
机动车调控有序、“进京”无序的现状,必须改变。记者在采访中了解到,北京市政府在倾听各方意见、经过反复论证后,相关管理 措施已呼之欲出。
“进京证”的使用,始于 1973 年,是为了方便进京办事的外地车辆,属于行政许可事项。现在,数量庞大的“进京证”已变了味。
一是挑战摇号政策,影响社会公平“。在京长期使用外地车的情况如此普遍,已经对北京市小客车数量调控暂行规定》政策的执行构成挑战。”一位北京市政协委员告诉记者。
“北京有限的道路资源和城市承载能力 决定了对机动车进行调控是必须的,也是世 界大城市管理的共同措施。”交通运输部公路 科学研究院首席工程师袁茂存说。按照《北京市“十三五”时期交通发展建设规划》和《北京市城市总体规划实施工作方案(2017 年— 2020年)》要求,到2020年底,北京全市机动 车保有量要控制在 630 万辆以内。这是从城市发展和资源承受能力等多方面考虑提出的硬指标。
二是影响大气污染治理硬措施。进入 2018 年,北京机动车排放已成为 PM2.5 的最 大“贡献”者;而 100 多万辆车的计划外贡献, 极大地增加了治理难度。
三是停车难矛盾增加。机动车年均增长 远高于道路建设里程的增速,按在册数统计,北京城镇地区居住停车位缺口总数高达 129 万。“我们小区停车位本来就紧张,现在还有很多外地牌照的汽车长期不上路占着车位。” 家住海淀区的刘女士说。
在近两年的北京市两会上,不断有代表委员提出强化外地车辆管理的建议。社会公众问卷调查显示,93%的北京市民支持加强 外地车辆管控。
怎么管?
宽严相济,刚性政策,柔性执行
对这么多“本地化”的非京牌车辆,怎么管起来,确实是个难题。
记者了解到,为了制定管理政策,北京市政府有关部门听取各方面意见,深入了解群众的实际困难,诸如用车者孩子上学、老人看病等实际需求。因此,即将推出的新政策,将是一个管理目标明确、又宽严相济的措施。
所谓宽,就是考虑到汽车是个大件,将给 足市民处置时间。据了解,管理措施发布后,会有一个一年半左右的过渡期,过了过渡期,才会限制使用。
所谓严,就是未来将限制每台车每年在京通行时间,就是一年只能申请几次“进京证”,不能无限制使用。而停放,也有细致规定,纳入限制。而北京市的交通管理系统也会升级,电子眼的功能将多样化。总之,“随便买,无限用”,是不可能了。原来的非京牌有车族,要早做打算。
与此同时,北京市正进一步加大公交、轨道交通建设力度,以多种措施服务市民出行。

[转发]关于2018年度北京积分落户及随迁子女高考问题的建议

强烈建议能分3-5年,分批解决2018年度符合积分落户基本条件(社保7年)的35万历史存量人员的落户问题。
1、2016年的人口统计数据显示,北京的人口老龄化率已经接近25%!按照联合国的传统标准是一个地区60岁以上老人达到总人口的10%,新标准是65岁老人占总人口的7%,即该地区视为进入老龄化社会。北京的老龄化率已经远远超过了联合国标准的3倍以上!由此而带来的系列问题已日益凸显,此处不予赘述。
2、2018年度符合积分落户基本条件(社保7年)的35万历史存量人员,最少的都已经在京生活、工作了7年以上,在京纳税,绝大多数人在京有自有住所,他们的存在已经在2300万政府控制常住人口数量之内,解决此部分人员的京户籍,不会给政府控制人口目标带来任何影响。
3、在京10年以上的常住人口(以社保缴纳10年为准),孩子不可避免的面临着孩子教育和高考问题,对于父母已经扎根在京的孩子来说。由于户籍问题造成的众多孩子从小学入学起就面临各种巨大的挑战,初中阶段起面临回老家读书,造成骨肉分离、留守儿童等系列问题的存在。教育为国家之根基所在,如果孩子连正常的教育和高考的资格都无法得到有效的、正常的保证,幸福感、获得感从何谈起?
4、 北京市早在2012年度就制定了《进城务工人员随迁子女接受义务教育后在京参加升学考试工作方案》(京政办发〔2012〕62号),根据方案要求,随迁子 女在京只能参加高职及大专的报考。该方案历经8年几乎没有任何变动,2016年度仅有422名考生提出申请。经审核,符合条件考生245人,参加高考报名 243人。2017年度只有391名提出申请,参加高考报名。与此同时,北京市高考报名人数已经从2006年度的12.6万人逐年下降至2016年度的 6.12万人!
5、自2016年8月11日北京市积分落户管理办法(试行)发布以来,截止2018年5月24日,众多北漂对于北京的2018年度 积分落户的正式实施给予了高度重视,在认真对待、积极参与的同时,也对于本年度“分数待定、数量待定”的情况给以了相应的疑虑和焦灼。很多爱心人士也从各 个角度对今年的分数线及可能的落户数量进行了大量分析,由于分数和指标数量的不确定,造成了大家的各种纠结及极为痛苦的焦灼状态。
6、2017年 以来,西安、武汉、长沙、海南等数十个城市发布的户籍新政,特别是天津近期发布的落户政策,已经对众多北漂造成了重大影响,我们可以预见,如果2018年 度的落户指标非常有限的情况下,以每年3000指标为例,消化这35万的历史存量需要30年以上的时间,而绝大多数人由于年龄及孩子教育问题的限制,不可 能无限期的等待下去。如果这种情况发生,将不可避免的造成众多北漂的中坚力量在在未来无奈的选择逃离北京。
7、
8、
9、
10、
综上所述,建议如下:
一、从优先解决历史存量的角度出发,建议第一年度适当扩大落户指标数量,用3到5年的时间分批消化截止2018年度已经满足基本条件的35万人员的落户问题。
二、 基于积分落户是面向广大的普通劳动者这一先决条件,建议简化落户相应指标,如将现在的年龄、社保、住房、学历、奖励等多项指标压减为以社保年限、自住房年 限等有限的指标,从各方面的调查(非官方)的情况来看,绝大部分北漂对于在京10年社保、10年住房可准予落户的条件表示认可,部分人员对于12年社保、 12年住房的相对非常苛刻的条件有能接受。
三、建议尽快对2012年度发布的《进城务工人员随迁子女接受义务教育后在京参加升学考试工作方案》 (京政办发〔2012〕62号)进行修订,建议以在京人员的社保(7年以上)、自住房(7年以上)及孩子在京学籍时间(6年以上)为主要条件,满足相应条 件的,应允许孩子在京参加普通高考,而非目前方案中设定的仅能参加高职及大专考试。
以上建议,是一位在京“北漂”20年的普通大众的个人意见,恳请****为盼。

来源:百度贴吧,水木积分落户群大神之作!!!​​​​

[北京积分落户]学历学位认证流程图

https://docs.qq.com/doc/BYLK7M0MXPP207FubW07rv9133VCbW17lhCt3

注意准备材料:
1、身份证正反面复印件-扫描件(图片格式)(可以手机拍照复印件后修改)
2、学位证书扫描件(图片格式)
3、毕业证书扫描件(图片格式)

注意事项:
本地上传图片失败 – 检查文件名是否为中文或者路径包含中文,将其修改为英文就可以上传了。

下面是学位证书认证申请的提示:

[转发]有记者问村上春树:“如何能保证持久的创作热情?”……

https://m.weibo.cn/status/4227498814281373?wm=3333_2001&from=1083393010&sourcetype=qq&featurecode=newtitle

有记者问村上春树:“如何能保证持久的创作热情?”村上便举了个跑步的例子。(村上本人每天长跑1小时)

“今天不想跑,所以才去跑,这才是长距离跑者的思维方式。”

其实村上这个道理可以用在任何地方。

“今天不想背单词,所以才去背单词,这才是考六级学生的思维方式。”

“今天不想写代码,所以才去写代码,这才是优秀编程员的思维方式。”

“今天不想吃药,所以才去吃药,这才是职业病人的思维方式。”

*作者:程浩,来源/知乎

=========================================

不想做什么才去做,是正确的思维方式。
不想做什么就不做什么,就是真正的自由。

=========================================