在Jetpack Compose中使用ExoPlayer进行直播流和音频均衡器

在Jetpack Compose中使用ExoPlayer进行直播流和音频均衡器

背景

ExoPlayer与Media3的能力结合,为Android应用程序播放多媒体内容提供了强大的解决方案。在本教程中,我们将介绍如何设置带有Media3的ExoPlayer来支持使用M3U8 URL进行直播流。此外,我们还将探讨如何集成音频均衡器,为用户提供个性化的音频体验。

使用ExoPlayer进行直播流涉及到利用ExoPlayer库的强大能力,在互联网上实时播放多媒体内容。

在这个探索中,我们将深入了解ExoPlayer如何实现无缝播放直播流URL的复杂性,为用户提供沉浸式体验。

ExoPlayer如何处理直播流?

使用ExoPlayer进行直播流主要围绕着有效地处理音频和视频内容的实时传输。该过程包括几个关键阶段:

  1. 内容源(Content Source):使用摄像机和麦克风捕获直播内容,然后将此直播流可用于流媒体。
  2. 编码(Encoding):捕获的内容被编码为适用于流媒体的数字格式。这涉及将原始音频和视频数据压缩并转换为与流媒体协议兼容的格式。
  3. 流媒体服务器(Streaming Server):编码数据发送到充当中央枢纽的流媒体服务器。该服务器通过向用户设备发送数据包来管理多个观众的直播内容分发。
  4. ExoPlayer集成(ExoPlayer Integration):将具有强大能力的ExoPlayer集成到应用程序中,以处理直播流的播放。应用程序获取直播流URL,并配置ExoPlayer以处理流媒体协议(例如HLS或DASH)。
  5. 观众设备(Viewer’s Device):用户通过各种设备访问直播流,包括智能手机、平板电脑、计算机或智能电视。观众设备上的ExoPlayer实例解码接收到的数据,使他们可以实时观看或收听直播内容。

设置ExoPlayer以进行直播流

要将ExoPlayer集成到我们的应用程序中以进行直播流,我们需要遵循以下关键步骤:

1.添加依赖项
在项目的build.gradle文件中包含必要的依赖项:

// Exoplayer dependencies
implementation("androidx.media3:media3-exoplayer:1.2.0")
implementation("androidx.media3:media3-ui:1.2.0")
implementation("androidx.media3:media3-exoplayer-hls:1.2.0")

这些依赖项确保应用程序可以利用ExoPlayer的功能进行直播流。

  1. 创建ExoPlayerManager
    我们将创建一个管理器类来处理ExoPlayer实例。这可以确保应用程序的整个生命周期中只有一个播放器实例。
object ExoPlayerManager {private var exoPlayer: ExoPlayer? = nullfun getExoPlayer(context: Context): ExoPlayer {if (exoPlayer == null) {exoPlayer = ExoPlayer.Builder(context).build()}return exoPlayer!!}fun releaseExoPlayer() {exoPlayer?.release()exoPlayer = null}
}
  1. Initialize ExoPlayer
    在您的Composable函数中,使用示例HLS流URL初始化ExoPlayer:
@Composable
fun LiveStreamingScreen() {// Obtain the current context and lifecycle owner using LocalContext and LocalLifecycleOwnerval context = LocalContext.currentval lifecycleOwner = LocalLifecycleOwner.current// Remember the ExoPlayer instance to persist across recompositionsval exoPlayer = remember { ExoPlayerManager.getExoPlayer(context) }// Launch an effect to initialize ExoPlayer and set up the media sourceLaunchedEffect(key1 = Unit) {// Create a data source factory for handling media requestsval dataSourceFactory = DefaultHttpDataSource.Factory()// Define the URI for the sample HLS streamval uri = Uri.Builder().encodedPath("http://sample.vodobox.net/skate_phantom_flex_4k/skate_phantom_flex_4k.m3u8").build()val mediaItem = MediaItem.Builder().setUri(uri).build()// Create an HlsMediaSource from the media item for handling HTTP Live Streaming (HLS) content val internetVideoSource =HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)exoPlayer.setMediaSource(internetVideoSource)exoPlayer.prepare()// Will be used in later implementation for EqualizerviewModel.onStart(exoPlayer.audioSessionId)}// ...
}
  1. 显示ExoPlayer视图

将ExoPlayer视图集成到您的Composable函数中

// ...
Box(modifier = Modifier.fillMaxSize()) {AndroidView(modifier =Modifier.fillMaxWidth().aspectRatio(1.4f).padding(top = 16.dp).background(Color.Black),factory = {PlayerView(context).apply {// Connect the ExoPlayer instance to the PlayerViewplayer = exoPlayer// Configure ExoPlayer settingsexoPlayer.repeatMode = Player.REPEAT_MODE_ONEexoPlayer.playWhenReady = falseuseController = true}})
}
// ...
  1. 观察生命周期事件并释放资源
    设置DisposableEffects来观察生命周期事件,并在组合函数被释放时释放ExoPlayer
// ...
// Observe lifecycle events (e.g., app resume and pause)
// and adjust ExoPlayer's playback state accordingly.
DisposableEffect(key1 = lifecycleOwner) {val observer = LifecycleEventObserver { _, event ->if (event == Lifecycle.Event.ON_RESUME) {exoPlayer.playWhenReady = true} else if (event == Lifecycle.Event.ON_PAUSE) {exoPlayer.playWhenReady = false}}lifecycleOwner.lifecycle.addObserver(observer)onDispose {lifecycleOwner.lifecycle.removeObserver(observer)}
}// Release the ExoPlayer when the composable is disposed
// This helps in proper resource management
DisposableEffect(key1 = Unit) {onDispose { ExoPlayerManager.releaseExoPlayer() }
}
// ...

设置音频均衡器

现在,让我们探讨如何在使用Jetpack Compose的Exoplayer设置中集成音频均衡器。这将允许用户通过调整预设均衡器设置或创建自定义均衡器配置来自定义音频体验。

音频均衡器通过提供对音频输出的细粒度控制来增强用户体验。

  1. 添加依赖项
    在项目的build.gradle文件中包含必要的依赖项:
    implementation("com.google.dagger:hilt-android:2.48")kapt("com.google.dagger:hilt-android-compiler:2.47")implementation("androidx.hilt:hilt-navigation-compose:1.1.0")//Gsonimplementation("com.google.code.gson:gson:2.9.1")

以上是示例依赖项,您可以根据您的项目需要进行相应的更改。

这些依赖项确保您的应用程序可以利用Hilt进行依赖注入和Gson以高效地将复杂数据存储在首选项中。

  1. 定义均衡器预设和增益值
    我们定义了一组预设,例如“Flat,” “Acoustic,” 和 “Rock,”,每个预设都有相应的增益值来控制音频频率。这些预设将作为用户自定义其音频体验的起点。
// Equalizer presets and gain values
val effectType = arrayListOf("Custom", "Flat", "Acoustic", "Dance","Hip Hop", "Jazz", "Pop", "Rock", "Podcast"
)// Constants for presets
const val PRESET_CUSTOM = 0
const val PRESET_FLAT = 1
const val PRESET_ACOUSTIC = 2
const val PRESET_DANCE_LOUNGE = 3
const val PRESET_HIP_HOP = 4
const val PRESET_JAZZ_BLUES = 5
const val PRESET_POP = 6
const val PRESET_ROCK = 7
const val PRESET_PODCAST = 8// Gain values for each preset
val FLAT = arrayListOf(0.0, 0.0, 0.0, 0.0, 0.0)
val ACOUSTIC = arrayListOf(0.44, 0.12, 0.12, 0.34, 0.2)
val DANCE = arrayListOf(0.52, 0.08, 0.28, 0.48, 0.06)
val HIP_HOPE = arrayListOf(0.44, 0.06, -0.14, 0.1, 0.38)
val JAZZ = arrayListOf(0.32, 0.0, 0.22, 0.1, 0.2)
val POP = arrayListOf(-0.14, 0.28, 0.38, 0.22, -0.2)
val ROCK = arrayListOf(0.38, 0.2, -0.04, 0.02, 0.34)
val PODCAST = arrayListOf(-0.12, 0.26, 0.36, 0.16, -0.2)

在上面的代码中,我们定义了一个EqualizerConfiguration数据类,它包含了预设设置和自定义频段设置的列表。EqualizerPreset类表示一个均衡器预设,包括名称和增益值的列表。CustomEqualizerBand类表示自定义的均衡器频段,包括频率和增益值。通过使用这些数据结构,我们可以轻松地管理和应用均衡器配置。

  1. 创建音频效果数据类
    音频效果数据类包含了关于所选效果类型及其相应增益值的重要信息。这个数据类充当用户偏好与音频均衡器实际实现之间的桥梁。
// 表示音频效果配置的数据类
data class AudioEffects(var selectedEffectType: Int = 0,var gainValues: ArrayList<Double>
)

在这里,selectedEffectType表示所选的音频预设,而gainValues则存储了不同频段的自定义增益值。这个数据类封装了用户的音频偏好设置。

  1. 创建AppModule进行依赖注入
    为了实现清晰模块化的依赖注入,我们引入了AppModule。这个模块使用@InstallIn(SingletonComponent::class)进行标注,提供了诸如Gson和SharedPreferences等必要的依赖项。
@Module
@InstallIn(SingletonComponent::class)
class AppModule {@Provides@Singletonfun provideGson(): Gson {val gsonBuilder = GsonBuilder()return gsonBuilder.create()}@Named(AUDIO_EFFECT_PREFERENCES)@Providesfun provideAudioEffectPreferences(application: Application): SharedPreferences {return application.getSharedPreferences(AUDIO_EFFECT_PREFERENCES, Context.MODE_PRIVATE)}
}

在这个模块中,provideGson提供了一个Gson的单例实例,而provideAudioEffectPreferences则提供了一个特定用于音频效果偏好设置的SharedPreferences实例。这个模块对于管理整个应用程序的依赖项非常重要。

  1. 使用SharedPreferencesGson实现均衡器偏好设置
    为了提供无缝的用户体验,我们将利用SharedPreferences来持久化与音频均衡器相关的用户偏好设置。此外,我们使用Gson进行高效的数据序列化,使我们能够将复杂的数据结构转换为易于存储和检索的格式。通过创建一个EqualizerPreferences类,我们确保用户不必重复设置他们的均衡器偏好设置。
const val AUDIO_EFFECT_PREFERENCES = "audio_effect_preferences"private const val AUDIO_EFFECT_IS_EQUALIZER_ENABLED = "is_equalizer_enabled"
private const val AUDIO_EFFECT_EQUALIZER_SETTING = "equalizer_audio_effect"
private const val AUDIO_EFFECT_LOWEST_BAND_LEVEL = "equalizer_lowest_band_level"@Singleton
class EqualizerPreferences
@Inject constructor(@param:Named(AUDIO_EFFECT_PREFERENCES) private val sharedPreferences: SharedPreferences,private val gson: Gson
) {var isEqualizerEnabled: Booleanget() = sharedPreferences.getBoolean(AUDIO_EFFECT_IS_EQUALIZER_ENABLED, false)set(isEnable) = sharedPreferences.edit().putBoolean(AUDIO_EFFECT_IS_EQUALIZER_ENABLED, isEnable).apply()// Getting and setting the user's audio preferencesvar audioEffects: AudioEffects?get() {val json = sharedPreferences.getString(AUDIO_EFFECT_EQUALIZER_SETTING, null)if (json != null) {try {return gson.fromJson(json, AudioEffects::class.java)} catch (t: Throwable) {t.printStackTrace()}}return null}set(audioEffects) {var json: String? = nullif (audioEffects != null) {json = gson.toJson(audioEffects)}sharedPreferences.edit().putString(AUDIO_EFFECT_EQUALIZER_SETTING, json).apply()}var lowestBandLevel: Intget() = sharedPreferences.getInt(AUDIO_EFFECT_LOWEST_BAND_LEVEL, 0)set(value) = sharedPreferences.edit().putInt(AUDIO_EFFECT_LOWEST_BAND_LEVEL, value).apply()
}

上述代码展示了如何使用SharedPreferences和Gson来保存和加载音频效果配置。saveAudioEffects方法将AudioEffects对象转换为JSON字符串,并将其保存在SharedPreferences中。loadAudioEffects方法从SharedPreferences中获取JSON字符串,并将其转换回AudioEffects对象。通过使用EqualizerPreferences类,我们可以方便地管理和访问均衡器偏好设置。

在这里,Gson在将我们的AudioEffects数据类转换为JSON字符串以存储在SharedPreferences中方面起着至关重要的作用。这确保了一种无缝且高效的方式来存储和检索复杂的数据结构。

  1. 创建一个音频均衡器ViewModel
    创建一个强大的AudioEqualizerViewModel,负责管理音频均衡器逻辑。这个ViewModel初始化均衡器,处理预设选择,并根据用户交互更新设置。
@HiltViewModel
class AudioEqualizerViewModel @Inject constructor(private val equalizerPreferences: EqualizerPreferences
) : ViewModel() {// MutableStateFlow to observe and emit changes in audio effectsval audioEffects = MutableStateFlow<AudioEffects?>(null)// Instance of the Equalizer class from the Android system libraryprivate var equalizer: Equalizer? = null// MutableStateFlow to observe and emit changes in the equalizer's enable/disable stateval enableEqualizer = MutableStateFlow(false)// Unique audio session ID associated with the Exoplayerprivate var audioSessionId = 0init {// Retrieve and set the initial equalizer enable/disable state and audio effects from preferencesenableEqualizer.value = equalizerPreferences.isEqualizerEnabledaudioEffects.tryEmit(equalizerPreferences.audioEffects)if (audioEffects.value == null) {audioEffects.tryEmit(AudioEffects(PRESET_FLAT, FLAT))}}// Will be called when exoplayer instance is created and we have audioSessionIdfun onStart(sessionId: Int) {audioSessionId = sessionIdequalizer?.enabled = enableEqualizer.valueequalizer = Equalizer(Int.MAX_VALUE, audioSessionId)// Set the lowest band level based on the equalizer's capabilitiesequalizerPreferences.lowestBandLevel = equalizer?.bandLevelRange?.get(0)?.toInt() ?: 0// Apply gain values to the equalizer based on the stored audio effectsaudioEffects.value?.gainValues?.forEachIndexed { index, value ->val bandLevel = (value * 1000).toInt().toShort()equalizer?.setBandLevel(index.toShort(), bandLevel)}}// Method called when a preset is selectedfun onSelectPreset(presetPosition: Int) {// Return if no audio effects are availableif (audioEffects.value == null) return// Determine the gain values based on the selected presetval gain = if (presetPosition == PRESET_CUSTOM) {ArrayList(audioEffects.value!!.gainValues)} else {ArrayList(getPresetGainValue(presetPosition))}// Update the audio effects with the selected preset and gain valuesaudioEffects.tryEmit(AudioEffects(presetPosition, gain))equalizerPreferences.audioEffects = audioEffects.value// Apply the gain values to the equalizerequalizer?.apply {gain.forEachIndexed { index, value ->val bandLevel = (value * 1000).toInt().toShort()setBandLevel(index.toShort(), bandLevel)}}}// Method called when a specific band level is changed by the userfun onBandLevelChanged(changedBand: Int, newGainValue: Int) {// Retrieve the lowest band level from preferencesval lowest = equalizerPreferences.lowestBandLevel// Calculate the new band levelval bandLevel = newGainValue.plus(lowest)// Apply the new band level to the equalizerequalizer?.setBandLevel(changedBand.toShort(), bandLevel.toShort())val list = ArrayList(audioEffects.value!!.gainValues)list[changedBand] = (newGainValue.toDouble() / 1000)audioEffects.tryEmit(AudioEffects(PRESET_CUSTOM,list))equalizerPreferences.audioEffects = audioEffects.value}// Method called to toggle the equalizer's enable/disable statefun toggleEqualizer() {enableEqualizer.tryEmit(!enableEqualizer.value)equalizer?.enabled = enableEqualizer.valueequalizerPreferences.isEqualizerEnabled = enableEqualizer.valueif (!enableEqualizer.value) {audioEffects.tryEmit(AudioEffects(PRESET_FLAT, FLAT))equalizerPreferences.audioEffects = audioEffects.value}}// Method to retrieve gain values for a specific presetprivate fun getPresetGainValue(index: Int): List<Double> {return when (index) {PRESET_FLAT -> FLATPRESET_ACOUSTIC -> ACOUSTICPRESET_DANCE_LOUNGE -> DANCEPRESET_HIP_HOP -> HIP_HOPEPRESET_JAZZ_BLUES -> JAZZPRESET_POP -> POPPRESET_ROCK -> ROCKPRESET_PODCAST -> PODCASTelse -> FLAT}}
}

这个ViewModel高效地管理音频均衡器的状态,处理用户交互,并确保使用SharedPreferences持久化用户偏好设置。

  1. 开发均衡器开关视图、预设视图和均衡器视图组件
    设计一个用户友好的均衡器开关、均衡器视图和预设视图组件,让用户可以可视化和调整均衡器设置。开关允许用户使用均衡器启用/禁用音频设置,而均衡器视图将包含不同频率带的滑块,提供高度可定制的音频体验。预设视图将包含一些预定义的效果类型,可以直接应用到均衡器上。

Switch View

Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween,verticalAlignment = Alignment.CenterVertically
) {Text(text = stringResource(R.string.equalizer_title_text),fontSize = MaterialTheme.typography.titleLarge.fontSize,fontWeight = FontWeight.SemiBold,color = Color.White)Switch(checked = enableEqualizer,onCheckedChange = { // Toggle the equalizer's enable/disable stateviewModel.toggleEqualizer() },colors =SwitchDefaults.colors(checkedTrackColor = Color.Black,checkedIconColor = Color.Black,uncheckedTrackColor = Color.White,uncheckedBorderColor = Color.Black,))
}

Equalizer SwitchView
EqualizerView

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EqualizerView(viewModel: AudioEqualizerViewModel) {// Frequency labels for the equalizer bandsval xAxisLabels = listOf("60Hz", "230Hz", "910Hz", "3kHz", "14kHz")// Collect the current state of audio effects from the ViewModelval audioEffects by viewModel.audioEffects.collectAsState()// Column layout to arrange UI elements verticallyColumn(modifier = Modifier.fillMaxWidth().fillMaxHeight().graphicsLayer {// Rotate the entire column to display frequency labels/sliders verticallyrotationZ = 270f},verticalArrangement = Arrangement.SpaceEvenly,horizontalAlignment = Alignment.CenterHorizontally) {// Iterate through frequency labels and create corresponding UI elementsfor (index in xAxisLabels.indices) {Row(modifier = Modifier.padding(top = 20.dp).width(220.dp)) {// Each frequency label and its corresponding slider are placed in a BoxBox {// Display the frequency label with rotationText(text = xAxisLabels[index],modifier = Modifier.wrapContentWidth().align(Alignment.CenterStart).rotate(90f),color = Color.White,fontSize = 8.sp,textAlign = TextAlign.Start)// Slider component for adjusting the gain value of each frequency bandSlider(modifier = Modifier.offset(x = 20.dp),// Bind the slider value to the corresponding gain value from the ViewModelvalue = audioEffects!!.gainValues[index].times(1000f).toFloat().coerceIn(-3000f, 3000f),onValueChange = {// Notify the ViewModel when a slider value changesviewModel.onBandLevelChanged(index, it.toInt())},valueRange = -3000f..3000f,colors = SliderDefaults.colors(thumbColor = Color.Black,activeTrackColor = Color.Black,inactiveTrackColor = Color.White),thumb = {// Customized appearance of the slider's thumbBox(modifier = Modifier.size(20.dp).border(1.dp,Color.White,CircleShape).clip(CircleShape).background(Color.Black, CircleShape))})}}}}
}

Equalizer View
PresetsView

@Composable
fun PresetsView(viewModel: AudioEqualizerViewModel) {// Collect the current state of audio effects from the ViewModelval audioEffects by viewModel.audioEffects.collectAsState()// Group the effect types into chunks of 4 for layoutval groupedList = effectType.chunked(4)// Row containing the title and dividersRow(verticalAlignment = Alignment.CenterVertically) {Divider(modifier = Modifier.weight(1f).height(4.dp).clip(RoundedCornerShape(4.dp)),color = Color.White,thickness = 1.dp)// Title textText(text = stringResource(R.string.presets_title_text),fontSize = MaterialTheme.typography.titleMedium.fontSize,fontWeight = FontWeight.Medium,color = Color.White,modifier = Modifier.wrapContentWidth().weight(0.5f).padding(4.dp).zIndex(1f),textAlign = TextAlign.Center)Divider(modifier = Modifier.weight(1f).height(4.dp).clip(RoundedCornerShape(4.dp)),color = Color.White,thickness = 1.dp)}Spacer(modifier = Modifier.height(20.dp))// Iterate through grouped effect types and create UI elementsfor (itemList in groupedList) {BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {// Calculate padding and spacing based on screen widthval horizontalPadding =if (maxWidth < 320.dp) 8.dp else if (maxWidth > 400.dp) 40.dp else 20.dpval horizontalSpacing = if (maxWidth > 400.dp) 24.dp else 16.dp// Row containing individual preset itemsRow(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),horizontalArrangement = Arrangement.spacedBy(space = horizontalSpacing,alignment = Alignment.CenterHorizontally),verticalAlignment = Alignment.CenterVertically) {for (item in itemList) {// Get the index of the current itemval index by remember {mutableIntStateOf(effectType.indexOf(item))}// Create a clickable preset itemBoxWithConstraints(modifier = Modifier.wrapContentSize().border(1.dp,if (index == audioEffects?.selectedEffectType) Color.White else Color.Black,RoundedCornerShape(40.dp)).clip(RoundedCornerShape(40.dp)).clickable {// Notify the ViewModel when a preset is selectedviewModel.onSelectPreset(index)}.background(if (index == audioEffects?.selectedEffectType) Color.Black else Color.White),contentAlignment = Alignment.Center) {// Display the preset item textText(text = item,style = MaterialTheme.typography.bodySmall,modifier = Modifier.padding(horizontal = horizontalPadding,vertical = 12.dp),fontSize = 14.sp,color = if (index == audioEffects?.selectedEffectType) Color.White else Color.Black,maxLines = 1,overflow = TextOverflow.Ellipsis)}}}}}
}


现在,让我们使用AnimatedVisibility从父组件中调用上述函数!

@Composable
fun AudioEqualizerScreen() {val viewModel = hiltViewModel<AudioEqualizerViewModel>()val enableEqualizer by viewModel.enableEqualizer.collectAsState()Column {Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween,verticalAlignment = Alignment.CenterVertically) {Text(text = stringResource(R.string.equalizer_title_text),fontSize = MaterialTheme.typography.titleLarge.fontSize,fontWeight = FontWeight.SemiBold,color = Color.White)Switch(checked = enableEqualizer,onCheckedChange = { viewModel.toggleEqualizer() },colors =SwitchDefaults.colors(checkedTrackColor = Color.Black,checkedIconColor = Color.Black,uncheckedTrackColor = Color.White,uncheckedBorderColor = Color.Black,))}AnimatedVisibility(visible = enableEqualizer,enter = fadeIn() + slideInVertically { fullHeight -> -fullHeight / 2 },exit = fadeOut() + slideOutVertically { fullHeight -> -fullHeight / 3 }) {EqualizerView(viewModel = viewModel)}AnimatedVisibility(visible = enableEqualizer,enter = fadeIn() + slideInVertically { fullHeight -> -fullHeight / 2 },exit = fadeOut() + slideOutVertically { fullHeight -> -fullHeight / 2 }) {PresetsView(viewModel)}}
}

Final View

结论

在本篇博客中,我们为在Jetpack Compose应用程序中设置ExoPlayer进行实时流式传输和集成音频均衡器打下了基础。这个组合为带有可定制均衡器设置的音频流提供了无缝的用户体验。

Github

https://github.com/cp-megh-l/audio-equalizer-compose

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/222937.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度剖析:Golang中结构体方法的高级应用

深度剖析&#xff1a;Golang中结构体方法的高级应用 引言结构体方法的基础回顾结构体的定义和用法方法的定义和绑定基本语法和用法 高级特性与应用封装、继承和多态方法集与接口的关系结构体方法的匿名字段和嵌入结构体 性能优化与最佳实践接收器类型的选择&#xff1a;指针还是…

2016年第五届数学建模国际赛小美赛C题对超级细菌的战争解题全过程文档及程序

2016年第五届数学建模国际赛小美赛 C题 对超级细菌的战争 原题再现&#xff1a; 最近有很多关于我们抗生素耐药性危机的讨论。进化出的能够抵抗抗生素的细菌每年杀死70万人&#xff0c;越来越强大的细菌正在世界各地传播。研究人员担心&#xff0c;我们将进入一个后抗生素时代…

MACBOOK 通过iterm2连接堡垒机跳转服务器

本公司是通过齐治堡垒机连接远程服务器的环境&#xff0c;因为连接过程中需要自动输入密码和选择主机&#xff0c;所以要使用expect工具&#xff0c;编写expect脚本remote.exp #!/usr/bin/expectif { $argc ! 7 } {send_user "usage: expect $argv0 \[JUMP_HOST\] \[JUM…

【贪心算法】之 摆动序列(中等题)

实际操作上&#xff0c;其实连删除的操作都不用做&#xff0c;因为题目要求的是最长摆动子序列的长度&#xff0c;所以只需要统计数组的峰值数量就可以了&#xff08;相当于是删除单一坡度上的节点&#xff0c;然后统计长度&#xff09; 这就是贪心所贪的地方&#xff0c;让峰…

Sentinel 流量治理组件教程

前言 官网首页&#xff1a;home | Sentinel (sentinelguard.io) 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形…

【Spring Security】认证密码加密Token令牌CSRF的使用详解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Spring Security》。&#x1f3af;&#x1f3af; …

日志服务 SLS 深度解析:拥抱云原生和 AI,基于 SLS 的可观测分析创新

云布道师 10 月 31 日&#xff0c;杭州云栖大会上&#xff0c;日志服务 SLS 研发负责人简志和产品经理孟威等人发表了《日志服务 SLS 深度解析&#xff1a;拥抱云原生和 AI&#xff0c;基于 SLS 的可观测分析创新》的主题演讲&#xff0c;对阿里云日志服务 SLS 产品服务创新以…

在Next.js和React中搭建Cesium项目

在Next.js和React中搭建Cesium项目&#xff0c;需要确保Cesium能够与服务端渲染(SSR)兼容&#xff0c;因为Next.js默认是SSR的。Cesium是一个基于WebGL的地理信息可视化库&#xff0c;通常用于在网页中展示三维地球或地图。下面是一个基本的步骤&#xff0c;用于在Next.js项目中…

【设计模式】命令模式

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、什么是命令模式&#xff1f; 二、命令模式的优点和应用场景 三、命令模式的要素和实现 3.1 命令 3.2 具体命令 3.3 接受者 …

51单片机的羽毛球计分器系统【含proteus仿真+程序+报告+原理图】

1、主要功能 该系统由AT89C51单片机LCD1602显示模块按键等模块构成。适用于羽毛球计分、乒乓球计分、篮球计分等相似项目。 可实现基本功能: 1、LCD1602液晶屏实时显示比赛信息 2、按键控制比赛的开始、暂停和结束&#xff0c;以及两位选手分数的加减。 本项目同时包含器件清…

2023 下半年系统架构设计师学习进度

文章目录 复习计划&#xff1a;每周350分钟第一周&#xff08;339分钟&#xff09;第二周&#xff08;265分钟&#xff09;第三周&#xff08;171分钟&#xff09;第四周&#xff08;214分钟&#xff09;第五周&#xff08;274分钟&#xff09;第六周&#xff08;191分钟&#…

图数据库NebulaGraph学习

1.图空间(Space)操作 1.1创建图空间&#xff0c;指定vid_type为整形 CREATE SPACE play_space (partition_num 10, replica_factor 1, vid_type INT64) COMMENT "运动员库表空间"; 1.2创建图空间&#xff0c;指定vid_type为字符串 CREATE SPACE play_space (…

深入解析Python装饰器及*args, **kwargs的妙用

深入解析Python装饰器及*args, **kwargs的妙用 简介&#xff1a; ​ 装饰器&#xff08;Decorator&#xff09;是 Python 中一种强大的语法特性&#xff0c;它允许在不修改原始函数代码的情况下&#xff0c;动态地扩展函数的功能。装饰器是函数或类&#xff0c;用于包装其他函…

Leetcode 剑指 Offer II 058. 我的日程安排表 I

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 请实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内…

DevC++ easyx实现图片拖动,一种悬浮窗实现原理与完整代码

翻出来之前写的代码&#xff0c; EasyxDevC开发地图编辑和游戏编辑代码工程文件附注释_哔哩哔哩_bilibili 每次把代码备份下来&#xff0c;等着有一天能够复用代码&#xff0c;产生新的价值。 结果最近这几天才来回顾记录emm “这是怎么搓出来的&#xff1f;”从10行代码到…

虚拟机安装

带你解密Linux的【Vm】-CSDN博客https://blog.csdn.net/lz17267861157/article/details/134031133

音画欣赏|《同杯万古尘》

《同杯万古尘》 尺寸&#xff1a;69x35cm 陈可之2023年绘 《拟古十二首-其九》 李白 生者为过客&#xff0c;死者为归人。 天地一逆旅&#xff0c;同悲万古尘。 月兔空捣药&#xff0c;扶桑已成薪。 白骨寂无言&#xff0c;青松岂知春。 前后更叹息&#xff0c;浮荣安足珍&am…

如何在Windows上搭建WebDAV服务并通过内网穿透实现公网访问

文章目录 前言1. 安装IIS必要WebDav组件2. 客户端测试3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网3.1 安装cpolar内网穿透3.2 配置WebDav公网访问地址 4. 映射本地盘符访问 前言 在Windows上如何搭建WebDav&#xff0c;并且结合cpolar的内网穿透工具实现在公网访…

基于python的excel检查和读写软件

软件版本&#xff1a;python3.6 窗口和界面gui代码&#xff1a; class mygui:def _init_(self):passdef run(self):root Tkinter.Tk()root.title(ExcelRun)max_w, max_h root.maxsize()root.geometry(f500x500{int((max_w - 500) / 2)}{int((max_h - 300) / 2)}) # 居中显示…

Linux 一键部署二进制Gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…