Logo

site iconDengQN

一个程序员。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

DengQN RSS 预览

【折腾】Gin支持mp4、mp3等文件断点加载

2024-11-28 16:26:15

本来mp4文件是直接nginx代理的,但是最近把图片音视频迁移到家里的minio里了,nginx不好直接代理到,索性实现个接口

直接上代码, 看注释得了。

复制代码
func videoServ(ctx *gin.Context) {
	filepath := ctx.Param("filepath")
	key := strings.Join([]string{config.CONFIG_INTANCE.DataDir, "videos", filepath}, "/")
	// 渠道object
	obj, err := storage.ReadFromMC(key)
	if err != nil {
		ctx.AbortWithError(http.StatusInternalServerError, err)
		return
	}
	// 取到文件大小
	fileInfo, err := obj.Stat()
	fileSize := fileInfo.Size
	// 获取浏览器的请求头
	rangeHeader := ctx.GetHeader("Range")

	// 空的就按全部大小来写入
	if rangeHeader == "" {
		// No Range header, send full file
		ctx.Header("Content-Length", strconv.FormatInt(fileInfo.Size, 10))
		ctx.Header("Accept-Ranges", "bytes")
		ctx.Header("Content-Type", "video/mp4")
		ctx.DataFromReader(http.StatusOK, fileInfo.Size, "video/mp4", obj, nil)
		return
	}

	// 解析Range头
	rangeSpec := strings.TrimPrefix(rangeHeader, "bytes=")
	startEnd := strings.SplitN(rangeSpec, "-", 2)
	// 获取开始值
	start, err := strconv.ParseInt(startEnd[0], 10, 64)
	if err != nil || start < 0 {
		ctx.JSON(http.StatusRequestedRangeNotSatisfiable, gin.H{"error": "Invalid start byte range"})
		return
	}
	// 获取结束值
	var end int64
	if len(startEnd) == 2 && startEnd[1] != "" {
		end, err = strconv.ParseInt(startEnd[1], 10, 64)
		// 超了就按最大
		if err != nil || end > fileSize-1 {
			end = fileSize - 1
		}
	} else {
		// 没有 也是最大
		end = fileSize - 1
	}
	// 值不对
	if start > end || start > fileSize-1 {
		ctx.JSON(http.StatusRequestedRangeNotSatisfiable, gin.H{"error": "Requested range not satisfiable"})
		return
	}

	// 设置响应头
	contentLength := end - start + 1
	// bytes start-end/total
	ctx.Header("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize))
	// acccept 头
	ctx.Header("Accept-Ranges", "bytes")
	ctx.Header("Content-Length", strconv.FormatInt(contentLength, 10))
	ctx.Header("Content-Type", "video/mp4")
	// 206 code
	ctx.Status(http.StatusPartialContent)

	// 跳到开始的地方
	_, err = obj.Seek(start, io.SeekStart)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not seek to start byte"})
		return
	}
	// 写入lenght长度
	io.CopyN(ctx.Writer, obj, contentLength)
	obj.Close()
}
image.png
QQ20241128-162830.png

【生活】中秋海边摄影记录

2024-09-26 23:53:38

去了个人少的海滩,阳光把沙子晒得烫脚。

image.png
image.png

大海有个比较好的地方元素比较少,稍微构图就好了。

image.png
偶尔也试试正方形构图

image.png

适马30mm 1.4 下的人像,非常给力。
image.png

【Crudboy日常】bash监控端口和启动服务

2024-09-14 16:19:02

有个机器上的服务可能会炸掉,在修复之前直接重启。懂不懂let it crash.哲学

bash 复制代码
#!/bin/bash
 
PORT=端口
SERVICE_NAME=服务名称
 
if ! nc -z localhost $PORT; then
    echo "Port $PORT is not active. Attempting to start $SERVICE_NAME..."

    systemctl start $SERVICE_NAME

    if systemctl is-active --quiet $SERVICE_NAME; then
        echo "$SERVICE_NAME has been started successfully."
    else
        echo "Failed to start $SERVICE_NAME."
    fi
else
    echo "Port $PORT is active."
fi

【Crudboy日常】Cesium实现流动线

2024-09-13 17:15:47

基本思路

关键点在于shader的编写

实现

通过按帧号的方式去计算采样的点位

js 复制代码
vec4 colorImage = texture(image, vec2(fract(st.s * 50.0 - speed * czm_frameNumber * 50.0 * 0.01), st.t));

被采样的贴图会被绘制到折线上。

js 复制代码
import * as Cesium from 'cesium';

/*
流动纹理线
 color 颜色
 duration 持续时间 毫秒
*/
function PolylineTrailLinkMaterialProperty (color, speed, duration, imageUrl) {
  this._definitionChanged = new Cesium.Event()
  this._color = undefined
  this._colorSubscription = undefined
  this.color = color
  this.speed = speed
  this.duration = duration
  this._time = new Date().getTime()
  this.imageUrl = imageUrl
}
Object.defineProperties(PolylineTrailLinkMaterialProperty.prototype, {
  isConstant: {
    get: function () {
      return false
    }
  },
  definitionChanged: {
    get: function () {
      return this._definitionChanged
    }
  },
  color: Cesium.createPropertyDescriptor('color')
})
PolylineTrailLinkMaterialProperty.prototype.getType = function (time) {
  return 'PolylineTrailLink'
}
PolylineTrailLinkMaterialProperty.prototype.getValue = function (time, result) {
  // debugger
  if (!Cesium.defined(result)) {
    result = {}
  }
  result.color = Cesium.Property.getValueOrClonedDefault(
    this._color,
    time,
    Cesium.Color.WHITE,
    result.color
  )
  result.image = this.imageUrl ? this.imageUrl : Cesium.Material.PolylineTrailLinkImage
  result.time = ((new Date().getTime() - this._time) % this.duration) / this.duration
  result.speed = this.speed
  return result
}
PolylineTrailLinkMaterialProperty.prototype.equals = function (other) {
  return (
    this === other ||
    (other instanceof PolylineTrailLinkMaterialProperty &&
      Property.equals(this._color, other._color))
  )
}
// Cesium.PolylineTrailLinkMaterialProperty = PolylineTrailLinkMaterialProperty
Cesium.Material.PolylineTrailLinkType = 'PolylineTrailLink'
Cesium.Material.PolylineTrailLinkImage = '/fire.png'
Cesium.Material.PolylineTrailLinkSource =
  `czm_material czm_getMaterial(czm_materialInput materialInput)
    {
        czm_material material = czm_getDefaultMaterial(materialInput);
        vec2 st = materialInput.st;
        vec4 colorImage = texture(image, vec2(fract(st.s * 50.0 - speed * czm_frameNumber * 50.0 * 0.01), st.t));
        material.alpha = colorImage.a * color.a;
        material.diffuse = (colorImage.rgb+color.rgb)/2.0;
        return material;
    }`
Cesium.Material._materialCache.addMaterial(
  Cesium.Material.PolylineTrailLinkType,
  {
    fabric: {
      type: Cesium.Material.PolylineTrailLinkType,
      uniforms: {
        color: new Cesium.Color(1.0, 0.0, 0.0, 0.5),
        image: Cesium.Material.PolylineTrailLinkImage,
        time: 0,
        speed: 0.008
      },
      source: Cesium.Material.PolylineTrailLinkSource
    },
    translucent: function (material) {
      return true
    }
  }
)

class FlowLine {
    
    constructor(viewer, options) {
        this.options = options
        this.viewer = viewer

        this.id = Cesium.Math.nextRandomNumber()
        if (options.id) {
          this.id = options.id
        }
    }

    play() {
        this.loopMaterial = new PolylineTrailLinkMaterialProperty(
            defaults(this.options.color, Cesium.Color.WHITE.withAlpha(0.5)),
            defaults(this.options.speed, 0.001),
            defaults(this.options.duration, 1750),
            defaults(this.options.image, "/guangpu.png") 
          )
        this.line = this.viewer.entities.add({
            name:  "flowline_" + this.id,
            polyline: {
                positions: this.options.positions,
                width: defaults(this.options.width, 10),
                material: this.loopMaterial //修改抛物线材质
            }
        })

    }

    destroy() {
      if (this.line) {
        this.viewer.entities.remove(this.line)
      }
  }
}

function defaults(value, defaultData) {
  return value ? value : defaultData
}

export {
    FlowLine
}

【Crudboy日常】Cesium+TweenJS实现轨迹回放

2024-09-13 16:41:19

基本思路:

  • 1、获取获取带时间戳的路径点
  • 2、绘制点图形和折线图形
  • 3、然后按顺序两两生成Tween线性插值并更新模型位置
  • 4、同时更新主视角相机位置和朝向姿态。
  • 5、其他周边操作,比如结束的回调等。

获取路径点

轨迹的来源多种多样,这里直接限定一个格式

js 复制代码
Array<{lon: Number, lat: Number, height: Number, timestamp: Number}>

需要提供带海拔高度的坐标,用于获取Catisian3;和当前时间,时间用于提供插值计算。

绘制图形

绘制点

这里使用Primitive的方式绘制,用entity绘制可能会有性能问题。点太多了

js 复制代码
drawPiont() {
    this.points = new Cesium.PointPrimitiveCollection({ blendOption: Cesium.BlendOption.TRANSLUCENT })

    this.options.points.map(p => {
        return {
            position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height),
            color: defaults(this.options.pointColor, Cesium.Color.RED.withAlpha(0.7)),
            pixelSize: defaults(this.options.pointWidth, 10)
        }
    }).forEach(p => this.points.add(p))

    this.viewer.scene.primitives.add(this.points)
}

需要打开TRANSLUCENT到时候显示的效果会好一点。

绘制折线

折线也采用Primitive的方式绘制。

js 复制代码
let degreeHeights = this.options.points.map(p => [p.lon, p.lat, p.height]).flat()

this.polyline = new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
        geometry: new Cesium.PolylineGeometry({
            positions: Cesium.Cartesian3.fromDegreesArrayHeights(degreeHeights),
            width: defaults(this.options.polylineWidth, 2),
        }),
        attributes: {
            color: Cesium.ColorGeometryInstanceAttribute.fromColor(defaults(this.options.polylineColor, Cesium.Color.YELLOW.withAlpha(0.5))),
        },
    }),
    appearance: new Cesium.PolylineColorAppearance({
        translucent: true
    }),
})
this.viewer.scene.primitives.add(this.polyline)

直接获取Cartisian3[] 绘制,并预留颜色、宽度等默认参数。

有可能需要绘制流动线。

js 复制代码
drawFlowLine() {
    this.polyline = new FlowLine(this.viewer, {
        // 线 Cartesian3[]
        positions: Cesium.Cartesian3.fromDegreesArrayHeights(this.options.points.map(p => [p.lon, p.lat, p.height]).flat()),
        // 线宽
        width: defaults(this.options.polylineWidth, 2),
        // 流动速度
        speed: defaults(this.options.flowLineSpeed, 0.01),
        // 底色
        color: defaults(this.options.flowLineColor, Cesium.Color.TEAL.withAlpha(0.5)),
        // 纹理
        image: defaults(this.options.flowLineImage, '/arrow-small.png')
    })
    this.polyline.play()
}

FlowLine是我自己封装的一个实现,下集再说吧

生成插值

直接遍历所有的坐标,按顺序两两生成Tween对象

js 复制代码
 this.group = new TWEEN.Group()

for (var pos = 1; pos < this.options.points.length; pos++) {
    let curr = [this.options.points[pos].lon, this.options.points[pos].lat, this.options.points[pos].height]
    let last = [this.options.points[lastPos].lon, this.options.points[lastPos].lat, this.options.points[lastPos].height]

    let temp = [last[0], last[1], last[2]]

    let sepAnimation = new TWEEN.Tween(last)
        .repeat(defaults(options?.repeat, 0))
        .interpolation(TWEEN.Interpolation.Bezier)
        .to(curr, (this.options.points[pos].timestamp - this.options.points[lastPos].timestamp) * options.speed)
        .onComplete(()=>{
            console.log("on complete--> ", pos);
            setTimeout(() => {
                this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
            }, 1000);
            if (callback) {
                callback()
            }
        })
        .onUpdate((op) => {})

    if (animateChain == null) {
        animateChain = sepAnimation
        animateChainHead = sepAnimation
    } else {
        animateChain.chain(sepAnimation)

        animateChain.onComplete(() => {
            sepAnimation.start()
        })

        animateChain = sepAnimation
    }
    this.group.add(sepAnimation)
    lastPos = pos
}

function animate() {
    that.groupAnimationRequest = requestAnimationFrame(animate);
    that.group.update();
}
animate()

首先生成分段动画sepAnimation, 动画开始的数据为上一次结果,结束为当前坐标位置,两个坐标的时间戳的差为动画的插值计算时长(预留播放倍速因子)。

TweenonUpdate里写上模型移动的算法,回调里会提供插值的数据。

onComplete里写上整个动画链结束后的回调,便于调用方处理后续事务。chain起来的动画序列只会执行onComplete一次。

把所有的分段动画都加到group里,把group放到requestAnimationFrame里加入渲染循环。

其中,Tween支持多个动画链接播放,使用chain()即可链接起来

接下来就是显示onUpdate里的逻辑

js 复制代码
var direction = Cesium.Cartesian3.subtract(Cesium.Cartesian3.fromDegrees(op[0], op[1], op[2]), Cesium.Cartesian3.fromDegrees(temp[0], temp[1], temp[2]), new Cesium.Cartesian3());
Cesium.Cartesian3.normalize(direction, direction);
var rotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(Cesium.Cartesian3.fromDegrees(temp[0], temp[1], temp[2]), direction);
this.model.orientation = Cesium.Quaternion.fromRotationMatrix(rotationMatrix)

先用先后两个点计算出向量direction。然后计算模型需要偏转+平移的矩阵rotationMatrix

模型移动了,当参数要求主视角跟随模型的时候,需要锁定主视角到模型

js 复制代码
let transform = Cesium.Matrix4.fromRotationTranslation(rotationMatrix, Cesium.Cartesian3.fromDegrees(temp[0], temp[1], temp[2]));

利用上边的rotationMatrix计算相机的朝向,然后利用lookAtTransform直接锁定过去。

js 复制代码
 this.viewer.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(
    Cesium.Math.toRadians(defaults(options.trackHeading, 90)),
    Cesium.Math.toRadians(defaults(options.trackPitch, -30.0)),
    defaults(options.trackRange, 50)
));

这里参数是正后方、距离50米俯视30度。
image.png