Android 媒体(音乐)播放通知栏

最新因为要做个视频,音乐播放的项目,因为需要做常驻通知,所以网上搜了一大堆,结果一个也不行,现在将最新的代码发出来,没有自定义通知栏,用的系统自带的媒体播放栏,Android最新版本已支持,不同Android厂商的展示效果会有差异,可以支持

1.自有播放器控制通知栏的播放,暂停,上一个,下一个,进度播放

2.通知栏控制播放器的播放,暂停,上一个,下一个,进度播放

1.首先是一个通知管理类(这里参照的其他博主的通知管理类,但是他那个MediaSession的构造方法在Android 14上会崩溃 PS:我估计在Andoird 12上都会崩溃,但是我只有Android14的手机)

java 复制代码
package com.anssy.bookvideo.utils.music;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

import com.anssy.bookvideo.R;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;


/**
 * 音频播放通知栏管理
 */
public class NotifyBuilderManager {
    private final String TAG = getClass().getSimpleName();
    public static final String ACTION_NEXT = "com.idujing.play.notify.next";// 下一首
    public static final String ACTION_PREV = "com.idujing.play.notify.prev";// 上一首
    public static final String ACTION_PLAY_PAUSE = "com.idujing.play.notify.play_state";// 播放暂停广播
    private static final int NOTIFICATION_ID = 0x123;
    private Service mContext;
    private Notification mNotification;
    private NotificationManager mNotificationManager;
    private NotificationCompat.Builder mNotificationBuilder;
    private MediaSessionManager mSessionManager;
    private PendingIntent mPendingPlay;
    private PendingIntent mPendingPre;
    private PendingIntent mPendingNext;
    private boolean isRunningForeground = false;

    public boolean isRunningForeground() {
        return isRunningForeground;
    }

    public NotifyBuilderManager(Service context) {
        this.mContext = context;
    }

    /**
     * 初始化通知栏
     */
    private void initNotify(String className) {
        mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 适配12.0及以上
        int flag  = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;;
        //绑定事件通过创建的具体广播去接收即可。
        Intent infoIntent = new Intent(mContext, clazz);
        PendingIntent pendingInfo = PendingIntent.getActivity(mContext, 0, infoIntent, flag);
        mSessionManager = new MediaSessionManager(mContext, null,clazz,pendingInfo);
        Intent preIntent = new Intent();
        preIntent.setAction(ACTION_PREV);
        mPendingPre = PendingIntent.getBroadcast(mContext, 1, preIntent, flag);
        Intent playIntent = new Intent();
        playIntent.setAction(ACTION_PLAY_PAUSE);
        mPendingPlay = PendingIntent.getBroadcast(mContext, 2, playIntent, flag);
        Intent nextIntent = new Intent();
        nextIntent.setAction(ACTION_NEXT);
        mPendingNext = PendingIntent.getBroadcast(mContext, 3, nextIntent, PendingIntent.FLAG_IMMUTABLE);

        androidx.media.app.NotificationCompat.MediaStyle style = new androidx.media.app.NotificationCompat.MediaStyle()
                .setShowActionsInCompactView(0, 1, 2)
                .setMediaSession(mSessionManager.getMediaSession())
                ;
        mNotificationBuilder = new NotificationCompat.Builder(mContext, initChannelId())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setContentIntent(pendingInfo)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setStyle(style);
        isRunningForeground = true;
    }

    /**
     * 创建Notification ChannelID
     *
     * @return 频道id
     */
    private String initChannelId() {
        // 通知渠道的id
        String id = "music_01";
        // 用户可以看到的通知渠道的名字.
        CharSequence name = mContext.getString(R.string.app_name);
        // 用户可以看到的通知渠道的描述
        String description = "通知栏播放控制";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_LOW;
            NotificationChannel channel = new NotificationChannel(id, name, importance);
            channel.setDescription(description);
            channel.enableLights(false);
            channel.enableVibration(false);
            mNotificationManager.createNotificationChannel(channel);
        }
        return id;
    }

    /**
     * 取消通知
     */
    public void cancelNotification() {
        if (mNotificationManager != null) {
            mContext.stopForeground(true);
            mNotificationManager.cancel(NOTIFICATION_ID);
            isRunningForeground = false;
        }
    }

    /**
     * 设置通知栏大图片
     */
    private void updateCoverSmall(String url) {

        Glide.with(mContext).asBitmap()
                .load(url)
                .into(new CustomTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                        mNotificationBuilder.setLargeIcon(resource);
                        mNotification = mNotificationBuilder.build();
                        mNotificationManager.notify(NOTIFICATION_ID, mNotification);
                    }

                    @Override
                    public void onLoadCleared(@Nullable Drawable placeholder) {
                    }

                    @Override
                    public void onLoadFailed(@Nullable Drawable errorDrawable) {
                        super.onLoadFailed(errorDrawable);
                        Log.e(TAG, "onLoadFailed: ");
                    }
                });
    }

    public void onDestroy(){
        mSessionManager.release();
    }

    /**
     * 更新状态栏通知
     */
    @SuppressLint("RestrictedApi")
    public void updateNotification(String className,boolean isMusicPlaying,String url,String name,String resourceName,long duration,long position) {
        if (mNotification == null) {
            initNotify(className);
        }
        mSessionManager.updateMetaData(isMusicPlaying,url,name,resourceName,duration,position);
        if (mNotificationBuilder != null) {
            int playButtonResId = isMusicPlaying
                    ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play;
            if (!mNotificationBuilder.mActions.isEmpty()) {
                mNotificationBuilder.mActions.clear();
            }
            mNotificationBuilder
                    .addAction(android.R.drawable.ic_media_previous, "Previous", mPendingPre) // #0
                    .addAction(playButtonResId, "Pause", mPendingPlay)  // #1
                    .addAction(android.R.drawable.ic_media_next, "Next", mPendingNext);
            mNotificationBuilder.setContentTitle(name);
            mNotificationBuilder.setContentText(resourceName);
            updateCoverSmall(url);
            mNotification = mNotificationBuilder.build();
            mContext.startForeground(NOTIFICATION_ID, mNotification);
            mNotificationManager.notify(NOTIFICATION_ID, mNotification);
        }
    }
}

2.MediaSession类,这个是播放通知栏控制播放器的操作类

java 复制代码
package com.anssy.bookvideo.utils.music;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;

/**
 * 主要管理Android 5.0以后线控和蓝牙远程控制播放
 */
public class MediaSessionManager {
    private static final String TAG = "MediaSessionManager";

    //指定可以接收的来自锁屏页面的按键信息
    private static final long MEDIA_SESSION_ACTIONS =
            PlaybackStateCompat.ACTION_PLAY
                    | PlaybackStateCompat.ACTION_PAUSE
                    | PlaybackStateCompat.ACTION_PLAY_PAUSE
                    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
                    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
                    | PlaybackStateCompat.ACTION_STOP
                    | PlaybackStateCompat.ACTION_SEEK_TO;
    private final Context mContext;
    private MediaSessionCompat mMediaSession;
    private Handler mHandler;

    public MediaSessionManager(Context context, Handler handler,Class<?> clazz, PendingIntent pendingIntent) {
        this.mContext = context;
        this.mHandler = handler;
        setupMediaSession(pendingIntent,clazz);
    }

    /**
     * 是否在播放
     *
     * @return
     */
    protected boolean isPlaying() {
        //具体去实现
        return false;
    }

    /**
     * 初始化并激活 MediaSession
     */
    private void setupMediaSession(PendingIntent pendingIntent,Class<?> clazz) {
        ComponentName componentName = new ComponentName(mContext,clazz);
        mMediaSession = new MediaSessionCompat(mContext, TAG, componentName,pendingIntent);
        //指明支持的按键信息类型
        mMediaSession.setFlags(
                MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
        );
        mMediaSession.setCallback(callback, mHandler);
        mMediaSession.setActive(true);
    }

    /**
     * 更新正在播放的音乐信息,切换歌曲时调用
     */
    public void updateMetaData(boolean isMusicPlaying,String url,String name,String resourceName,long duration,long position) {

        MediaMetadataCompat.Builder metaDta = new MediaMetadataCompat.Builder()
                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, name)
                .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, resourceName)
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, resourceName)
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, resourceName)
                .putLong(MediaMetadataCompat.METADATA_KEY_DURATION,duration);
        mMediaSession.setMetadata(metaDta.build());

        int state = isMusicPlaying ? PlaybackStateCompat.STATE_PLAYING :
                PlaybackStateCompat.STATE_PAUSED;
        mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
                .setActions(MEDIA_SESSION_ACTIONS)
                .setState(state, position, 1)
                .build());

   //锁屏页封面设置,高本版没有效果,因为通知栏权限调整。
        Glide.with(mContext).asBitmap().
                load(url)
                .into(new CustomTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                        metaDta.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, resource);
                        mMediaSession.setMetadata(metaDta.build());
                    }

                    @Override
                    public void onLoadCleared(@Nullable Drawable placeholder) {

                    }
                });


    }

    public MediaSessionCompat.Token getMediaSession() {
        return mMediaSession.getSessionToken();
    }

    /**
     * 释放MediaSession,退出播放器时调用
     */
    public void release() {
        mMediaSession.setCallback(null);
        mMediaSession.setActive(false);
        mMediaSession.release();
    }

    /**
     * API 21 以上 耳机多媒体按钮监听 MediaSessionCompat.Callback
     */
    private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {

        @Override
        public void onPlay() {
           //具体自己实现
            Intent intent = new Intent();
            intent.setAction(NotifyBuilderManager.ACTION_PLAY_PAUSE);
            mContext.sendBroadcast(intent);
        }

        @Override
        public void onPause() {
            Intent intent = new Intent();
            intent.setAction(NotifyBuilderManager.ACTION_PLAY_PAUSE);
            mContext.sendBroadcast(intent);
        }

        @Override
        public void onSkipToNext() {
            Intent intent = new Intent();
            intent.setAction(NotifyBuilderManager.ACTION_NEXT);
            mContext.sendBroadcast(intent);
        }

        @Override
        public void onSkipToPrevious() {
            Intent intent = new Intent();
            intent.setAction(NotifyBuilderManager.ACTION_PREV);
            mContext.sendBroadcast(intent);
        }

        @Override
        public void onStop() {

        }

        @Override
        public void onSeekTo(long pos) {
            Intent intent = new Intent();
            intent.setAction("seek");
            intent.putExtra("pos",pos);
            mContext.sendBroadcast(intent);
        }
    };

}

3.BroadCastReceiver(广播)

java 复制代码
package com.anssy.bookvideo.utils.music;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

/**
 * @Description TODO
 * @Author yulu
 * @CreateTime 2025年03月26日 11:22:30
 */

public class MusicBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
       
    }
}

4.Service(常驻服务及后台运行)

java 复制代码
package com.anssy.bookvideo.utils.music;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

/**
 * @Description TODO
 * @Author yulu
 * @CreateTime 2025年03月26日 11:27:31
 */

public class MusicService extends Service {
    private NotifyBuilderManager notifyBuilderManager;

    @SuppressLint("NewApi")
    public void onCreate() {
        super.onCreate();
        notifyBuilderManager = new NotifyBuilderManager(this);
    }
    public void updateData(String className,boolean isMusicPlaying,String url,String name,String resourceName,long duration,long position){
        notifyBuilderManager.updateNotification(className,isMusicPlaying,url,name,resourceName,duration,position);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        return START_STICKY;

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        notifyBuilderManager.onDestroy();
        notifyBuilderManager.cancelNotification();
    }


    /**
     * 返回一个Binder对象
     */
    @Override
    public IBinder onBind(Intent intent) {
        return new MsgBinder();
    }

    public class MsgBinder extends Binder {
        /**
         * 获取当前Service的实例
         * @return
         */
        public MusicService getService(){
            return MusicService.this;
        }
    }
}

至此,核心类已经创建完成

Java调用,请自己根据自己的播放器来实现相关逻辑,播放器对通知栏的控制,请调用Service方法里的updateData()方法

java 复制代码
public class VideoPlayActivity extends BaseActivity{

private MusicBroadcastReceiver musicBroadcastReceiver;
private boolean isPlaying;

  @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      
        musicBroadcastReceiver = new MusicBroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent != null) {
                    String action = intent.getAction();
                    switch (action) {
                        case "com.idujing.play.notify.play_state":
                            if (videoView.isPlaying()) {
                                videoView.pause();
                            } else {
                                videoView.resume();
                            }
                            break;
                        case "com.idujing.play.notify.next":
                            nextData();
                            break;
                        case "com.idujing.play.notify.prev":
                            lastData();
                            break;
                        case "seek":
                            long pos = intent.getLongExtra("pos", 0);
                            videoView.seekTo(pos);
                            break;
                    }
                }
            }
        };
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(NotifyBuilderManager.ACTION_PLAY_PAUSE);
        intentFilter.addAction(NotifyBuilderManager.ACTION_NEXT);
        intentFilter.addAction(NotifyBuilderManager.ACTION_PREV);
        intentFilter.addAction("seek");
        registerReceiver(musicBroadcastReceiver,intentFilter, RECEIVER_EXPORTED);
        if("有通知权限"){
            startMusicService()
        }  
    }
    

     /**
     * 常驻监听
     */
    private void  startMusicService() {
        mIntent = new Intent(this, MusicService.class);
        bindService(mIntent, conn, Context.BIND_AUTO_CREATE);
    }


    private Intent mIntent;
    private MusicService msgService;

    private ServiceConnection conn = new  ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            msgService = ((MusicService.MsgBinder)service).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    //仅做展示
    private addVideoListener(){
  videoView.addOnStateChangeListener(new BaseVideoView.OnStateChangeListener() {
            @Override
            public void onPlayerStateChanged(int playerState) {

            }

            @Override
            public void onPlayStateChanged(int playState) {
                switch (playState){
                    case BaseVideoView.STATE_PLAYBACK_COMPLETED:
                        isPlaying = false;
                        if (mCurrentPlayType.equals("1")){
                            nextData();
                        }else if (mCurrentPlayType.equals("2")){
                            videoView.replay(true);
                        }
                        if (null!=mIntent){
                            msgService.updateData(VideoPlayActivity.class.getName(),videoView.isPlaying(),mCurrentEntry.imageUrl,mCurrentEntry.name,mCurrentEntry.cateName,videoView.getDuration(),videoView.getCurrentPosition());
                        }
                        break;
                    case BaseVideoView.STATE_BUFFERED:
                        if (null!=mIntent){
                            msgService.updateData(VideoPlayActivity.class.getName(),videoView.isPlaying(),mCurrentEntry.imageUrl,mCurrentEntry.name,mCurrentEntry.cateName,videoView.getDuration(),videoView.getCurrentPosition());
                        }
                        break;
                    case BaseVideoView.STATE_PLAYING:
                        if (!isPlaying){
                            isPlaying = true;
                            if (null!=mIntent){
                                msgService.updateData(VideoPlayActivity.class.getName(),isPlaying,mCurrentEntry.imageUrl,mCurrentEntry.name,mCurrentEntry.cateName,videoView.getDuration(),videoView.getCurrentPosition());
                            }
                        }
                        break;
                    case BaseVideoView.STATE_PAUSED:
                        isPlaying = false;
                        if (null!=mIntent){
                            msgService.updateData(VideoPlayActivity.class.getName(),isPlaying,mCurrentEntry.imageUrl,mCurrentEntry.name,mCurrentEntry.cateName,videoView.getDuration(),videoView.getCurrentPosition());
                        }
                        break;
                }

            }
        });
        
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null!=mIntent){
            stopService(mIntent);
        }
        unregisterReceiver(musicBroadcastReceiver);
       
    }
}

    

2.kotlin调用

Kotlin 复制代码
class FormationDetailOneActivity : BaseActivity() { 
 private lateinit var musicBroadcastReceiver: MusicBroadcastReceiver
 private var isPlayIng = false

   
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mViewBinding = ActivityFormationDetailOneBinding.inflate(layoutInflater)
        setContentView(mViewBinding.root)
        musicBroadcastReceiver = object : MusicBroadcastReceiver(){
            override fun onReceive(context: Context?, intent: Intent?) {
                if (intent!=null){
                    val action = intent.action
                    if (action=="com.idujing.play.notify.play_state"){
                        if (videoView.isPlaying){
                            videoView.pause()
                        }else{
                            videoView.resume()
                        }
                    }else if (action=="com.idujing.play.notify.next"){
                        (mFragList.get(currentPage) as DetailOneFrag).nextData()
                    }else if (action=="com.idujing.play.notify.prev"){
                        (mFragList.get(currentPage) as DetailOneFrag).lastData()
                    }else if (action=="seek"){
                        val pos = intent.getLongExtra("pos", 0)
                        videoView.seekTo(pos)
                    }
                }
            }
        }
        val  intentFilter = IntentFilter()
        intentFilter.addAction(NotifyBuilderManager.ACTION_PLAY_PAUSE)
        intentFilter.addAction(NotifyBuilderManager.ACTION_NEXT)
        intentFilter.addAction(NotifyBuilderManager.ACTION_PREV)
        intentFilter.addAction("seek")
        registerReceiver(musicBroadcastReceiver,intentFilter, RECEIVER_EXPORTED)
        if("有通知权限和数据加载完成"){
            startMusicService()
        }
    }   
    

      /**
     * 常驻监听
     */
    private fun  startMusicService() {
            mIntent = Intent(this, MusicService::class.java)
          bindService(mIntent!!, conn, Context.BIND_AUTO_CREATE);

        }


    private var mIntent: Intent? = null
    private var msgService : MusicService? = null

    private var conn: ServiceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName) {
        }

        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            //返回一个MsgService对象
            msgService = (service as MsgBinder).service

        }
    }

    //仅演示
private fun addListener(){
     //播放器监听
        videoView.addOnStateChangeListener(object : BaseVideoView.OnStateChangeListener {
            override fun onPlayerStateChanged(playerState: Int) {

            }
            override fun onPlayStateChanged(playState: Int) {
                when(playState){
                    BaseVideoView.STATE_PLAYING->{
                        if(!isPlayIng){
                            isPlayIng = true
                           
                            if (null!=mIntent){
                                msgService!!.updateData(this@FormationDetailOneActivity.javaClass.name,videoView.isPlaying,Constants.IMAGE_URL+imagePath,mCurrentData!!.detailName,resourceName,videoView.duration,videoView.currentPosition)
                            }
                        }

                    }
                    BaseVideoView.STATE_BUFFERED->{
                       
                        if (null!=mIntent){
                            msgService!!.updateData(this@FormationDetailOneActivity.javaClass.name,videoView.isPlaying,Constants.IMAGE_URL+imagePath,mCurrentData!!.detailName,resourceName,videoView.duration,videoView.currentPosition)
                        }
                    }
                 
                    BaseVideoView.STATE_PAUSED->{
                        isPlayIng = false
                        
                        if (null!=mIntent){
                            msgService!!.updateData(this@FormationDetailOneActivity.javaClass.name,videoView.isPlaying,Constants.IMAGE_URL+imagePath,mCurrentData!!.detailName,resourceName,videoView.duration,videoView.currentPosition)
                        }
                    }
                    BaseVideoView.STATE_PLAYBACK_COMPLETED->{
                        isPlayIng = false
                       
                        if (null!=mIntent){
                            msgService!!.updateData(this@FormationDetailOneActivity.javaClass.name,videoView.isPlaying,Constants.IMAGE_URL+imagePath,mCurrentData!!.detailName,resourceName,videoView.duration,videoView.currentPosition)
                        }
                       
                    }

                }


            }

        })
    }

        override fun onDestroy() {
        super.onDestroy()
        if (null!=mIntent){
            stopService(mIntent)
        }
        unregisterReceiver(musicBroadcastReceiver)
       
    }

}

3.需要的权限及注册Service

XML 复制代码
  <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>





<service android:name="com.anssy.bookvideo.utils.music.MusicService"
            android:foregroundServiceType="mediaPlayback"
           />

4.效果

1.逍遥模拟器 (Android 9 )

2.荣耀 X50I(Android14)

相关推荐
Sayai2 小时前
ComfyUI+混元视频实战:12G 显存跑 13G 模型,5 分钟出片(国产开源,附模型与工作流)
人工智能·aigc·音视频·comfyui·hunyuanvideo·混元视频
朱涛的自习室3 小时前
我给 Gemini 4 和 DeepSeek 搞了套「末位淘汰」,AI 干活会更猛吗?(Day 3)
android·前端·后端
ACP广源盛139246256735 小时前
项目评估记录:4K 视频墙方案调研,GSV9001E @ACP参数与工程问题记录
硬件架构·音视频·硬件工程·视频·国产芯片
应用市场9 小时前
离线小游戏做“每日挑战”:日期当随机种子,整局存成 JSON,再加一个“我不是机器人”
android·kotlin·游戏开发
一铭1119910 小时前
海豹办公 1.9.15 跨端办公软件实践 文档兼容 Access 数据库与移动 WebView 排错
android·数据库·electron·开源软件·access
AI视频工具指南10 小时前
怎么用 AI 做一两分钟的小故事视频?从结尾倒推故事
人工智能·aigc·音视频
geats人山人海10 小时前
java 7异常
android·java·开发语言
AI视频工具指南11 小时前
AI短剧怎么做?一个矛盾怎样接着讲三集
人工智能·aigc·音视频
酷虎软件11 小时前
视频加字幕怎么制作
java·前端·音视频
2601_9620710011 小时前
Kimi K3 vs DeepSeek V4-Pro实测对比:2026年同一入口十道题,耗时与token消耗差在哪
android·java·数据库